4
votes

Consider the code:

class Test {
public:
    template<int N> auto foo() {}
    template<> auto foo<0>() { return 7;  }

    template<int N> void bar() {}
    template<> int bar<0>() { return 7;  }
};

I have tested the code with different compilers (through Compiler Explorer).

In case of Clang 7.0.0 foo compiles, while bar is giving an error:

:8:20: error: no function template matches function template specialization 'bar'

template<> int bar<0>() { return 7;  }

               ^

:7:26: note: candidate template ignored: could not match 'void ()' against 'int ()'

template<int N> void bar() {};

                     ^

Visual C++ agrees (MSVC 19 2017 RTW):

(8): error C2912: explicit specialization 'int Test::bar(void)' is not a specialization of a function template

gcc 8.2 does not compile any of the code (though the reason is probably a bug in C++17 support:

:5:14: error: explicit specialization in non-namespace scope 'class Test'

 template<> auto foo<0>() { return 7;  };

          ^

:5:28: error: template-id 'foo<0>' in declaration of primary template

 template<> auto foo<0>() { return 7;  };

                        ^

:7:26: error: too many template-parameter-lists

 template<int N> void bar() {};

                      ^~~

:8:14: error: explicit specialization in non-namespace scope 'class Test'

 template<> int bar<0>() { return 7;  }

          ^

:8:20: error: expected ';' at end of member declaration

 template<> int bar<0>() { return 7;  }

                ^~~

                   ;

:8:23: error: expected unqualified-id before '<' token

 template<> int bar<0>() { return 7;  }

                   ^

What is the correct interpretation here? Can I have a different return type for different method specializations (and why only with auto, but not while specifying them explicitly)? With my limited understanding of auto and templates I would go with saying "no". I don't understand why would using auto instead of explicitly naming the return type allow to have different return type for different specializations.

However, those codes are simplified versions of the code that I have found elsewhere, so maybe my interpretation is incorrect - and in that case I would be grateful for the explanation why different return type is allowed when auto is used for specialization, while explicitly naming the type seems to be forbidden.

1
@DanM. The linked question does not pertain to the disparity between using auto and explicitly naming the return type in the specialisation.lukeg
but it does. It quotes the standard on this exact matter AFAIU: eel.is/c++draft/dcl.spec.auto#11 (which causes the difference in compiler behaviour). Your example also contains this error: stackoverflow.com/questions/2097811/…Dan M.
@DanM. As far as I understand, the quoted draft says that one should use auto for specialization when the base template uses auto and one should name a concrete type when specializing a template that uses a concrete type. It is silent, as far as I can tell, on the issue of changing the return type in specialisation in any of those two cases. As for the second part of your comment (linking stackoverflow.com/questions/2097811/c): my code differs from that in that question because in my code class Test is not a template, what was a problem in that question.lukeg
while the example in SO question has a templated class it likewise applies to a regular one. See stackoverflow.com/questions/5777236 . Though it should work with c++17 and on, but GCC still doesn't support it: gcc.gnu.org/bugzilla/show_bug.cgi?id=85282 The standard explains why foo is allowed. As for the error with bar - simply naming the function the same way as a template doesn't mean it's a specialization. You can only specialize the template parameters. int isn't a template parameter for bar (but you can make it so, in which case it should work).Dan M.

1 Answers

1
votes

There are several different problems with the example code.

1) GCC fails to implement CWG 727 (required in C++17): https://gcc.gnu.org/bugzilla/show_bug.cgi?id=85282 which causes error: explicit specialization in non-namespace scope 'class Test'

2) If we ignore that, the example code can be simplified to just

template<int N> auto foo() {}
template<> auto foo<0>() { return 7; }

template<int N> void bar() {}
template<> int bar<0>() { return 7; }

which still exhibits the same errors. Now all compilers agree on the output. They compile foos and error out on the bar specialization.

why different return type is allowed when auto is used for specialization

It is allowed by a standard to specialize functions with auto return type if the specializations also have auto placeholder

Redeclarations or specializations of a function or function template with a declared return type that uses a placeholder type shall also use that placeholder, not a deduced type

http://eel.is/c++draft/dcl.spec.auto#11

So by virtue of this specialization conforming to the standard (being similar to one of the given examples) and not being explicitly forbidden anywhere it's allowed.

As for the error with bar, the standard says that return type is a part of function template signature:

⟨function template⟩ name, parameter type list ([dcl.fct]), enclosing namespace (if any), return type, template-head, and trailing requires-clause ([dcl.decl]) (if any)

http://eel.is/c++draft/defns.signature.templ

As such, in compiler's eyes template<> int bar<0>() { return 7; } is a specialization of template<... N> int bar(); template (notice the return type). But it wasn't declared previously (specialization can't precede declaration) so compilation fails! If you add template<int N> int bar(); then it'll compile (but complain about call ambiguity if you try to call bar).

Basically, you can't change the function signature in a specialization, you can only specialize (duh) template parameters (which should also be exactly the same as in declaration, since it's also part of the signature).

Can I have a template specialization with explicitly declared return type different from the base template at all

As explained - you can't change the function template signature which means you can't change the return type. BUT, you can specialize the return type if it depends on a template parameter!

Consider

template<int N, typename R = void> R bar() {}
template<> int bar<0>() { return 7; }
// bar<0, int> is deduced, see: http://eel.is/c++draft/temp.expl.spec#10

This is allowed but it has a disadvantage of having to write bar<0, int> when you want to make a call to a specialization: https://godbolt.org/z/4lrL62

This can be worked around by making the type conditional in the original declaration: https://godbolt.org/z/i2aQ5Z

But it'll quickly become cumbersome to maintain once the number of specializations grows.

Another, perhaps a bit more maintainable option, would be to return something like ret_type<N>::type and specialize that along with bar. But it's still won't be as clean as with just using auto.