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

14
下面经过简化的代码在最新版本的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移到前向声明中,一切都能完美运行(U按预期被推导为int),好像只有在前向声明中的默认模板参数被考虑进用于推导指南的虚构函数模板集合时才会生效。
我尝试阅读标准措辞,但对于这种特定情况并没有很大的帮助。当生成虚构函数模板时,只使用前向声明中的默认模板参数是预期的行为,还是这是编译器的一个错误?

不确定是否是一个错误,但可能的解决方法是创建用户定义的推导指南:template <class Wrapper> wrapper(Wrapper) -> wrapper<Wrapper>; - W.F.
1
创建显式实例确实也可以解决问题,但将默认模板参数移动到前向声明中也可以。我会选择前向声明的解决方案,以便在使用C++14编译时能够正常工作,并在编译为C++17时提供免费的推导指南,而无需检查功能测试宏 :) - Morwenn
很好的发现...这确实听起来像一个bug... - W.F.
2
似乎与此错误相关:https://bugs.llvm.org/show_bug.cgi?id=10147。 - xskxzr
@xskxzr 你说得对,它看起来非常相似。谢谢你提供的链接 :) - Morwenn
1个回答

7

这并不是标准的引用1,但我足够自信将其视为答案。

根据cppreference,在默认模板参数上:

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 ]


4
此处的C++标准文档[temp.param]/10讨论了模板参数的顺序,它们应该按照字典顺序排序。如果有多个模板参数具有相同的名称,则根据它们在模板参数列表中的出现顺序进行排序。 - Oliv
@Oliv 谢谢,我已经将它加入到答案中了。 - YSC
听起来对我来说还不错。如果我有时间的话,我会提交一个错误报告。谢谢! - Morwenn

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接