4

Consider the code below:

#include <iostream>

class Test {
   public:
    Test() {std::cout << "I am called\n";};

    template <typename T>
    Test(T t);
};

template <typename T> Test::Test(T t){ std::cout << t << "\n";};

// Explicit template instantiation
template Test::Test<int>(int);
template Test::Test<double>(double);

int main() {
    Test test;
    Test test2(12342);
    Test test3(3.1415);
    return 0;
}

This code compiles and works fine with GCC (my version is 13.2). Output is as I expect:

I am called
12342
3.1415

Clang (my version is 16.0.0) throws an error and the code does not compile:

<source>:14:16: error: qualified reference to 'Test' is a constructor name rather than a type in this context
template Test::Test<int>(int);
               ^
<source>:14:20: error: expected unqualified-id
template Test::Test<int>(int);
                   ^
<source>:15:16: error: qualified reference to 'Test' is a constructor name rather than a type in this context
template Test::Test<double>(double);
               ^
<source>:15:20: error: expected unqualified-id
template Test::Test<double>(double);
                   ^
4 errors generated.

Can anyone help me understand the problem or direct me to a source?

pppery
  • 3,731
  • 22
  • 33
  • 46
  • Any reason you need the explicit instantiation ? The code as presented would work without it. Alternatively, use template type deduction, which works for me with Clang on godbolt: `template Test::Test(int);` Templated constructors for a non-templated class handle a little funky in general. – nick Jul 28 '23 at 06:48
  • 2
    @nick This is a minimal working example. You can assume that the class is declared in a header file, implementations are defined in an implementation file and the main function is defined in a separate file. In this case, the compiler cannot know which invocations are needed. This is a scenario I encountered out in real wild world. I found a way to workaround it, but I want to understand the difference between compiler behaviours. – Abdullah Ali Sivas Jul 28 '23 at 07:48
  • Related: https://stackoverflow.com/questions/3960849/c-template-constructor, and https://stackoverflow.com/questions/2786946/c-invoke-explicit-template-constructor – pptaszni Jul 28 '23 at 10:06
  • Thanks @pptaszni, these examples make the answer below clearer :) – Abdullah Ali Sivas Jul 29 '23 at 14:53

1 Answers1

1

A template argument list can't be given to a constructor per [temp.arg.explicit]/1 requiring the template argument list for a function specialization to follow the "function template name", which constructors do not have. Constructors are named via the injected-class-name of their containing class instead of having a name like other function templates. (Constructors can be referred to in declarative forms only. When writing e.g. Test(1, 2) as an expression, then Test refers to the class Test, not its constructor, which can't be named or called explicitly.)

Resolution of CWG 581 also reaffirms that in 2019 for C++20 and as a defect report against previous standard editions with wording that explicitly excludes template argument lists on constructors in [temp.arg.explicit]/2.

So the explicit instantiation can't have a template argument list, but it also doesn't need one. Instead

template Test::Test(int);
template Test::Test(double);

is sufficient. Template argument deduction is done on explicit instantiation declarations for function templates, so this will choose the expected specializations.

This is not an actual restriction in the language, because a constructor template specialization that can't have its template arguments deduced this way would be useless as it also could never be chosen by overload resolution to be called. Constructors are never called with explicitly specified template arguments either.

GCC shouldn't accept the code without diagnostic to be conforming. Before CWG 581 the intent might not have been completely clear, but the resolution seems very clear in its wording to me. GCC lists the defect report's implementation as unknown status. Clang's behavior is conforming.

user17732522
  • 53,019
  • 2
  • 56
  • 105
  • Hi, thanks for the answer. An answer to another question mentions a note in C++ standard at `14.8.1/7`, in short, template argument list follows the function template name, but constructors do not have a function name. Another answer explains in `Foo f = Foo();`, `` is the template argument list for the type not for the constructor, e.g. here Foo is not a function name, it is a type. But the way I wrote it, it would have to be a constructor name. So Clang is standard compliant here and GCC is lenient. – Abdullah Ali Sivas Jul 29 '23 at 15:19
  • C++ Standards Discussion item "581. Can a templated constructor be explicitly instantiated or specialized?" mentions "... the current wording does not permit an explicit specialization of a constructor template. Nevertheless, there is implementation divergence ...". Apparently, in October 2018, they decided "to allow template arguments on the constructor name but not something like C::C::f." Proposed resolution from February 2019 is to amend the standards document with "Template arguments shall not be specified when referring to a specialization of a constructor template ..." – Abdullah Ali Sivas Jul 29 '23 at 15:23
  • Can you please expand your answer "A template argument list can't be given to a constructor. "? Otherwise, it sounds like some omnipotent entity is arbitrarily putting in rules and there is no way to reason about them. – Abdullah Ali Sivas Jul 29 '23 at 15:27
  • @AbdullahAliSivas I have included the details. "_Apparently, in October 2018, they decided "to allow template arguments on the constructor name but not something like C::C::f._": They seem to have changed their mind with the actual resolution of the CWG issue. – user17732522 Jul 29 '23 at 18:54
  • Thank you very much for the elaboration and the extra information. I accepted and upvoted your answer :) – Abdullah Ali Sivas Jul 29 '23 at 21:32