Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Member definition of partially specialized classes

I'm trying to define member functions of partially specialized class templates, but different compilers have wildly different opinions of what I'm allowed to do and why.

Let's take this slowly and start with something that works for all main compilers (all = gcc, clang and msvc):

#include <concepts>
#include <type_traits>

template <class T>
concept Integer
= std::is_same_v<T,int> || std::is_same_v<T,unsigned int>;

template <class T>
concept FloatingPoint
= std::is_same_v<T,float> || std::is_same_v<T,double>;


template <class T>
struct Foo
{
    T get() {return 0;}
};

template <Integer T>
struct Foo<T>
{
    T get(){ return 0; }
};

template <FloatingPoint T>
struct Foo<T>
{
    T get(){ return 0; }
};

int main()
{
    Foo<char>().get();
    Foo<int>().get();
    Foo<float>().get();
}

Examples on godbolt: gcc, clang, msvc

Cool, but I want to separate declarations and definitons of the member functions. Let's move the definiton one of the specialized classes. Examples: gcc, clang, msvc. GCC and MSVC both work fine, but Clang fails to correctly match the member definition to the correct declaration.

No worries, I was never planning to use Clang anyway. Let's try separating the other specialized definition from its declaration too. Example: gcc, clang, msvc. GCC continues to deliver, Clang gives more of the same errors, and MSVC complains that I've redefined a member...

Who is right? Do I have a fundamental misconception about partial template specialization? How do I get this working on MSVC?

like image 937
Kelemen Máté Avatar asked Jan 11 '21 13:01

Kelemen Máté


People also ask

What is the other name of full specialization?

Explanation: explicit specialization is another name of full specialization.

How do you define a template class?

A class template can be declared without being defined by using an elaborated type specifier. For example: template<class L, class T> class Key; This reserves the name as a class template name. All template declarations for a class template must have the same types and number of template arguments.

When we specialize a function template it is called?

To do so, we can use a function template specialization (sometimes called a full or explicit function template specialization) to create a specialized version of the print() function for type double.

How do C++ templates work?

Templates in c++ is defined as a blueprint or formula for creating a generic class or a function. To simply put, you can create a single function or single class to work with different data types using templates. C++ template is also known as generic functions or classes which is a very powerful feature in C++.


1 Answers

Who is right?

GCC is correct to accept all the cases, whereas Clang and MSVC are wrong to reject them; out-of class-definitions of partial specializations shall be matched to their declarations by equivalent template-head:s, which includes constraints, if any.

The template-head of the primary template of Foo is not equivalent to the template-head of the two constrained partial specializations of it, and the definition of the primary template shall therefor not conflict with out-of-class definitions to member functions of constrained specializations of it.

Do I have a fundamental misconception about partial template specialization?

No, not from the looks of this question.

How do I get this working on MSVC?

As MSVC, like Clang, seems to (currently) have problems differentiating template-head:s for out-of-class definitions (particularly shown when partially specializing a class template by means of constraints), you will need to avoid the latter whilst compiling with MSVC (for now). As there may not be an associated MSVC bug report, you may want to consider filing one.


Details

As per [temp.spec.partial.general]/4 a partial specialization may indeed be constrained:

A partial specialization may be constrained ([temp.constr]). [Example 2:

template<typename T> concept C = true;

template<typename T> struct X { };
template<typename T> struct X<T*> { };          // #1
template<C T> struct X<T> { };                  // #2

[...] — end example]

as long as the declaration of the primary template precedes it.

We may minimize your example into the following:

template <typename>
concept C = true;

template <typename T>
struct S;

template <C T>
struct S<T> { void f(); };

template <C T> 
void S<T>::f() {}

which GCC accepts but Clang rejects

prog.cc:12:12: error: out-of-line definition of f from class S<T> without definition

void S<T>::f() {}
     ~~~~~~^

meaning Clang interprets the out-of-class definition

template <C T> 
void S<T>::f() {}

as having a template-head that is equivalent to that of the primary template, which is wrong, as the template-head includes template-parameter:s which in turn includes type-parameter:s which in turn includes type-constraints:s, if any.

The equivalence of template-head:s is governed by [temp.over.link]/6, which includes constraints [emphasis mine]:

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.

Finally, albeit non-normative, both Example 2 of [temp.mem] and

[...] A member template can be defined within or outside its class definition or class template definition. A member template of a class template that is defined outside of its class template definition shall be specified with a template-head equivalent to that of the class template followed by a template-head equivalent to that of the member template ([temp.over.link]). [...]

[ Example 2:

template<typename T> concept C1 = true;
template<typename T> concept C2 = sizeof(T) <= 4;

template<C1 T> struct S {
  template<C2 U> void f(U);
  template<C2 U> void g(U);
};

template<C1 T> template<C2 U>
void S<T>::f(U) { }             // OK
template<C1 T> template<typename U>
void S<T>::g(U) { }             // error: no matching function in S<T>

— end example]

and Example 2 of [temp.class.general]:

[ Example 2:

// ...

template<typename T> concept C = true;
template<typename T> concept D = true;

template<C T> struct S {
  void f();
  void g();
  void h();
  template<D U> struct Inner;
};

template<C A> void S<A>::f() { }        // OK: template-heads match
template<typename T> void S<T>::g() { } // error: no matching declaration for S<T>

template<typename T> requires C<T>      // ill-formed, no diagnostic required: template-heads are
void S<T>::h() { }                      // functionally equivalent but not equivalent

template<C X> template<D Y>
struct S<X>::Inner { };                 // OK

— end example]

show example of out-of-class definitions of member functions of constrained class template specializations.

This is, particularly, Clang bug:

  • Bug 48020 - Defining constrained member function out of line is rejected

I'm not aware whether there is a similar bug report for MSVC or not.

like image 56
dfrib Avatar answered Nov 13 '22 01:11

dfrib