隐式转换运算符与模板构造函数 - 谁应该优先考虑?

时间:2018-06-12 16:14:59

标签: c++ language-lawyer c++17 copy-elision conversion-operator

请考虑以下代码段:

template <typename>
struct dependent_false { static constexpr auto value = false; };

struct foo
{
    foo() { }

    template <typename T>
    foo(const T&) { static_assert(dependent_false<T>::value, ""); }
};

struct proxy
{
    operator foo() { return foo{};  }
};

int main()
{
    (void) foo{proxy{}};
}

使用-std=c++17进行编译时:

  • clang++(trunk)成功编译代码;

  • g++(trunk)无法编译代码 - 它实例化foo(const T&)

使用-std=c++11进行编译时,两个编译器都拒绝该代码。 C ++ 17中新的 prvalue 具体化规则可能会影响此处的行为。

live example on godbolt.org

这里的正确行为是什么?

  • 标准是否保证将foo::foo(const T&)实例化(或不实例化)?

  • 标准是否保证隐式转换运算符优先于foo::foo(const T&)的调用,无论它是否被实例化?

1 个答案:

答案 0 :(得分:4)

这是CWG 2327

  

考虑一个例子:

struct Cat {};
struct Dog { operator Cat(); };

Dog d;
Cat c(d);
     

这是11.6 [dcl.init] bullet 17.6.2:

     
    

否则,如果初始化是直接初始化,或者它是复制初始化,其中源类型的cv-nonqualified版本与目标类相同的类,或派生类,构造函数是考虑。枚举适用的构造函数(16.3.1.3 [over.match.ctor]),并通过重载决策(16.3 [over.match])选择最佳构造函数。调用所选的构造函数来初始化对象,初始化表达式或表达式列表作为其参数。如果没有构造函数适用,或者重载决策不明确,则初始化是不正确的。

  
     

重载分辨率选择Cat的移动构造函数。初始化Cat&amp;&amp;根据11.6.3 [dcl.init.ref]子弹5.2.1.2,构造函数的参数产生一个临时的。这排除了这种情况下复制省略的可能性。

     

这似乎是对保证副本省略的措辞变更的疏忽。在这种情况下,我们应该同时考虑构造函数和转换函数,就像复制初始化一样,但是我们需要确保不会引入任何新的问题或含糊之处。

我相信clang实现了这个隐含的变化(因此认为转换函数更好地匹配)并且gcc没有(因此从未真正考虑转换函数)。

根据标准,gcc是正确的。