c++templatesfunction-templatesnon-type-template-parameter

Is the type of a non-type template parameter part of the function signature?


Regardless of whether this is a good idea or not, is it allowed in C++ to have two template functions, that differ only in the type of a non-type template parameter.

I am asking this question, because MSVC and GCC/Clang behave differently in this respect.

I have these template functions:

template <E1 x>
void fun() {}

template <E2 x>
void fun() {}

When defining the types E1 and E2 as enums and calling the functions, this does compile with GCC/Clang, but does not compile with MSVC 2022 ("ambiguous call to overloaded function"):

enum E1 { A = 1 };
enum E2 { B = 1 };

void test() {
    fun<A>();
    fun<B>();
}

I guess this is because the compilers do name mangling differently and MSVC loses the type information in the process, so both are just a calls to fun<1>().

On the other hand, when I convert the enums to enum classes it also compiles on MSVC:

// defining the enums:
enum class E1 { A = 1 };
enum class E2 { B = 1 };

// calling the functions like this:
fun<E1::A>();
fun<E2::B>();

So, which compiler behaves correctly according to the spec? And is the latter version using enum classes a valid solution for the problem?

Edit:

I just realized using scoped enums (enum class) is not a working solution for MSVC. It does compile, but linking still fails due to duplicate functions: (fatal error LNK1179: invalid or corrupt file: duplicate COMDAT '??$fun@$00@@YAXXZ').


Solution

  • is it allowed in C++ to have two template functions, that differ only in the type of a non-type template parameter.

    Yes.

    The condition for two function template declarations to correspond is given in [basic.scope.scope]/4:

    Two declarations correspond if they (re)introduce the same name, both declare constructors, or both declare destructors, unless

    • either is a using-declarator, or
    • one declares a type (not a typedef-name) and the other declares a variable, non-static data member other than of an anonymous union ([class.union.anon]), enumerator, function, or function template, or
    • each declares a function or function template and they do not declare corresponding overloads.

    Two function or function template declarations declare corresponding overloads if

    • both declare functions with the same non-object-parameter-type-list, equivalent ([temp.over.link]) trailing requires-clauses (if any, except as specified in [temp.friend]), and, if both are non-static members, they have corresponding object parameters, or
    • both declare function templates with corresponding signatures and equivalent template-heads and trailing requires-clauses (if any).

    If two function or function template definitions correspond and have the same target scope, then they are considered two declarations of the same entity (provided that linkage conditions are met; [basic.link]/8). Note that for non-template functions, the return type is not taken into account when determining whether declarations correspond. So, for example, int foo(); and void foo(); can correspond, which means they both declare the same function but with inconsistent types and this is ill-formed under [basic.link]/11. We want to know whether we have a similar situation for the OP's fun overloads. If they correspond, that's probably bad news!

    Anyway, as we can see from the above, two function templates named fun correspond only if they declare corresponding overloads. They declare corresponding overloads only if their signatures correspond, their template-heads are equivalent, and their trailing requires-clauses are equivalent. In this case there are no requires-clauses so that part is automatically satisfied.

    [temp.over.link]/6 defines what it means for two template-heads to be equivalent:

    Two template-heads are equivalent if their template-parameter-lists have the same length, corresponding template-parameters are equivalent and are both declared with type-constraints that are equivalent if either template-parameter is declared with a type-constraint, and if either template-head has a requires-clause, they both have requires-clauses and the corresponding constraint-expressions are equivalent.

    Two template-parameters are equivalent under the following conditions:

    • they declare template parameters of the same kind,
    • if either declares a template parameter pack, they both do,
    • if they declare non-type template parameters, they have equivalent types ignoring the use of type-constraints for placeholder types, and
    • if they declare template template parameters, their template parameters are equivalent.

    When determining whether types or type-constraints are equivalent, the rules above are used to compare expressions involving template parameters. Two template-heads are functionally equivalent if they accept and are satisfied by ([temp.constr.constr]) the same set of template argument lists.

    Types E1 and E2 are different, so they are not equivalent, which means the two declarations of fun have inequivalent template-heads and, therefore, do not correspond. They declare two different overloads of fun.

    Note that fun<A>(); cannot call the overload that takes a template parameter of type E2 because there's no implicit conversion from A to E2. Similarly, fun<B>() can't call the overload that takes a template parameter of type E1. The ambiguity error is a bug in MSVC. Using scoped enumerations (enum class) is a fine way to work around the issue.