andreasxp
andreasxp

Reputation: 174

Clang does not notice default template parameters

Background

According to the C++ standard, when forward-declaring a template type with default template parameters, each of them can appear in one declaration only. For example:

// GOOD example
template <class T = void>
class Example;    // forward-declaration

template <class T>
class Example {}; // definition
// GOOD example
template <class T>
class Example;    // forward-declaration

template <class T = void>
class Example {}; // definition
// BAD example
template <class T = void>
class Example;    // forward-declaration

template <class T = void> // ERROR: template parameter redefines default argument
class Example {}; // definition

Problem

In my code I have a lot of forward declaration in different files, so it makes sense to put my default parameters in the definition:

// foo.hpp, bar.hpp, baz.hpp, etc.
template <class T>
class Example;
// example.hpp
template <class T = void>
class Example {};

and, as expected, it all works well everywhere... except clang! I narrowed the problem down to this:
In clang, if the class template has default parameters, but they are not declared in the first forward declaration of that class, and when declaring an instance of that class no angle brackets are specified, clang ignores the default parameter and raises an error "no viable constructor or deduction guide for deduction of template arguments of ...".

Example

// GOOD example
template <class T>
class Example;

template <class T = void>
class Example {};

int main() {
    Example e; // error: no viable constructor or deduction guide for deduction of template arguments of 'Example'
}

Question

Who is right in this case: clang or the other compilers? Is this a compiler bug? How can I circumvent this issue (apart from partial solutions I described above)? I've found #10147 (and related stackoverflow questions), but it's about template template params and also is marked as fixed over a year ago.

Edit

This looks like a bug, and is now reported on LLVM bugtracker (#40488).

Upvotes: 14

Views: 945

Answers (3)

Jans
Jans

Reputation: 11250

Considering the following:

[temp.param]/12 - The set of default template-arguments available for use is obtained by merging the default arguments from all prior declarations of the template in the same way default function arguments are [ Example:

template<class T1, class T2 = int> class A;
template<class T1 = int, class T2> class A;

is equivalent to

template<class T1 = int, class T2 = int> class A;

— end example ]

The default arguments available for

template <class T>
class Example;

template <class T = void>
class Example {};

will be the defaults arguments in the definition of Example. The two declarations above will be equivalent to have a single declaration as

template <class T = void>
class Example {};

which will effectively allow doing Example e.

The original code should be accepted. As a workaround and already suggested in max66's answer, you can provide a deduction guide that uses the default argument

Example() -> Example<>;

Upvotes: 3

Oliv
Oliv

Reputation: 18081

The standard does not make distinction whether a default template argument is defined in the definition or a declaration of a template.

Because Clang accepts the code when the default argument appears in the declaration, but not in the definition, at least one of this two behaviors is wrong. Considering [over.match.class.deduct]/1.1.1:

The template parameters are the template parameters of C followed by the template parameters (including default template arguments) of the constructor, if any.

, I am tempted to say that Clang should use the default template argument.

I think that you could avoid this bug by following a common practice:

  1. If a declaration must be forwarded, create a dedicated header file for this forward declaration.

  2. Define default arguments in this forward declaration file

  3. Also include this file in the header file that provides the definition of the template.

As an example see iosfwd: libstdc++/iosfwd

Upvotes: 0

max66
max66

Reputation: 66240

I don't know who's right but...

How can I circumvent this issue (apart from partial solutions I described above)?

What about adding the following deduction rule?

Example() -> Example<>;

The following code compile (C++17, obviously) with both g++ and clang++

template <class T>
class Example;

template <class T = void>
class Example {};

Example() -> Example<>;

int main() {
    Example e;
}

Upvotes: 5

Related Questions