Search code examples
c++templatesnon-typetemplate-argument-deduction

Why can't the compiler deduce my template value from the function argument?


The following won't compile:

enum E {A,B,C};
template<E m> 
void foo(E m) {}

int main() {
    foo(A);
    return 0;
}

The errors I get are:

  • declaration of 'E m' : void foo(E m) {} : shadows template parm 'E m'
  • error: no matching function for call to 'foo(E)' : foo(A);
  • candidate is: template void foo(E) : void foo(E m) {}
  • template argument deduction/substitution failed: couldn't deduce template parameter 'm' : foo(A);

I don't understand what exactly is wrong here. Why can't the compiler deduce the template parameter from the function argument?

What do I need to do to make this work?


Solution

  • If you want a run-time argument, use:

    void foo(E m) {}
    

    Which has a value m of type E. (note: no template<E m> required)

    If you want a compile-time argument, use:

    template<E m> 
    void foo() {}
    

    and call:

    foo<A>();
    

    Or if you want foo to work for all enumeration types:

    template<typename E> 
    void foo(E m) {}
    

    And probably check for enums with

    static_assert(std::is_enum<E>::value, "E is not an enumeration");
    

    in the function body. (You can also use SFINAE to remove foo from an overload set if needed, ask if you need help with that)


    Update: Explaining your original code and what is wrong with it:

    template<E m> void foo(E m) {}
    //       ^^^ (1)       ^^^ (2)
    

    (1) is a compile-time argument m of type E, (2) is a run-time argument also called m and also of type E. Since it has the same name, the second argument hides the first one. Using the name m in your function will only access the second argument and you can not access the first one. Now consider:

    template<E m1> void foo(E m2) {}
    

    Now you can access the arguments under different names, i.e., m1 and m2. If you call the function like this:

    foo<A>(B);
    

    then m1 is A and m2 is B. And still both are of the same fixed type E. They are independent parameters and the value of the run-time parameter will not be used for the compile-time parameter.

    Depending on which kind or argument you need (compile-time or run-time), you simply leave out the one you don't need and you end up with one of the above implementations.

    It's not 100% clear to me where your real problem is, which is not untypical for beginners as it is admittedly hard to describe a problem you don't really understand, but make sure you start by understanding the difference between the compile-time and the run-time parameters and how they can be used.