Search code examples
c++templatestemplate-argument-deduction

Template parameters that require deduced argument *before* user arguments


I have a function where I'd like the arguments partially-deduced and the remaining, mostly non-type arguments, are given by the user (as a way of enforcing them being given at compile-time). However, the type of this non-type user-sourced argument is deduced, therefor it must come before the user arguments. That breaks the ability of the user to have the first argument deduced. I have a small example below, that does not compile, demonstrating what I'm talking about.

template <typename T, T N>
class int_const {};

template <typename T, T M, T N>
auto add(int_const<T, N> a) {
    return int_const<T, N + M>();
}

int main(void) {
    int_const<int, 1> a;
    add<32>(a); 
    // add<int, 32>(a); does compile, but requires the user to know or extract that first argument
    return 0;
}

Is there anyway to support the template function call as it is seen in main()?


Solution

  • If you can use C++17, you can use auto for M value

    template <auto M, typename T, T N>
    auto add(int_const<T, N> a) {
        return int_const<T, N + M>();
    }
    

    So you can call it as follows

    add<32>(a);
    

    Before C++17... well, I don't see a way without explicating also the type.

    As pointed by Jarod42, auto M intercept also values of different types.

    If you want impose that the type of M is exactly T, you can use SFINAE; by example, as follows

    template <auto M, typename T, T N, 
              std::enable_if_t<std::is_same_v<T, decltype(M)>, bool> = true>
    auto add(int_const<T, N> a) {
        return int_const<T, N + M>();
    }
    

    So you get errors from

    add<32u>(a);
    add<short{32}>(a);
    

    But maybe you can relax the requirement and accept also that decltype(M) isn't exactly T but also simply that M is narrowing convertible to T.

    Maybe

    template <auto M, typename T, T N>
    auto add(int_const<T, N> a) {
        return int_const<T, N + T{M}>();
    } // .......................^^^^
    

    so

    add<32u>(a);
    add<short{32}>(a);
    

    are compiled because 32u and short{32} are narrowing convertible to int where

    add<(unsigned long)(-1)>(a);
    

    gives a compilation error because (unsigned long)(-1) (usually the bigger possible value for an unsigned long) can't be narrowed to int.