Class 模板参数推导和默认模板参数

Class template argument deduction and default template parameters

以下精简代码不适用于最新的 clang++5,但被 g++7 接受:

template<typename Wrapped, typename U>
struct wrapper;

template<typename Wrapped, typename U=int>
struct wrapper
{
    wrapper() = default;

    // Automatic deduction guide
    constexpr explicit wrapper(Wrapped) noexcept {}
};

int main()
{
    struct {} dummy;
    constexpr auto wrapped = wrapper(dummy);
}

失败并显示以下错误消息:

<source>:18:30: error: no viable constructor or deduction guide for deduction of template arguments of 'wrapper'
    constexpr auto wrapped = wrapper(dummy);
                             ^
<source>:12:24: note: candidate template ignored: couldn't infer template argument 'U'
    constexpr explicit wrapper(Wrapped) noexcept {}
                       ^
<source>:4:8: note: candidate template ignored: could not match 'wrapper<Wrapped, U>' against '(anonymous struct at <source>:17:5)'
struct wrapper;
       ^
<source>:9:5: note: candidate function template not viable: requires 0 arguments, but 1 was provided
    wrapper() = default;
    ^

但是,如果我将默认模板参数 =int 从 class 模板定义移动到前向声明,一切都会完美运行(U 被推断为 int 作为预期),就好像在创建演绎指南使用的虚构函数模板集时只考虑了前向声明中的默认模板参数。

我试图阅读标准措辞,但对于这个特定案例,我无法从中得到太多信息。生成虚构函数模板时,仅在前向声明中采用默认模板参数是预期行为,还是编译器错误?

这不是标准本身的引用1,但我有足够的信心将其视为答案。

根据 cppreference,在 Default template arguments:

Default template arguments that appear in the declarations and the definition are merged similarly to default function arguments:

template<typename T1, typename T2 = int> class A;
template<typename T1 = int, typename T2> class A;
// the above is the same as the following:
template<typename T1 = int, typename T2 = int> class A;

But the same parameter cannot be given default arguments twice in the same scope

template<typename T = int> class X;
template<typename T = int> class X {}; // error

这意味着一个隐含的规则:模板类型参数可以在模板声明或模板定义中互换地指定默认类型

clang++5 表现出的行为绝对是一个错误。


1) 用户 Oliv 提供:

[temp.param]/10

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 ([dcl.fct.default]). [ 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 ]