【问题标题】:Initialize class' template (aggregate type) member with aggregate initializer but without extra parenthesis使用聚合初始化器初始化类的模板(聚合类型)成员,但没有额外的括号
【发布时间】:2019-04-13 18:16:36
【问题描述】:

拥有此代码:

struct Vec3 {
    int x;
    int y;
    int z;
};

template <typename T>
class myProperty {
public:
   myProperty(const T& initValue) : m_value{initValue} {}
private:
    T m_value;
};

创建myProperty类型对象时:

myProperty<int> ip{1};
myProperty<Vec3> vp1{{1, 2, 3}};
// myProperty<Vec3> vp2{1, 2, 3}; ERROR: myProperty doesn't have a matching constructor.

有没有一种优雅的方式让vp2 初始化工作?将myProperty 专门用于Vec3 是一种矫枉过正。

【问题讨论】:

  • 是否让myProperty 本身成为一个聚合选项? (即删除构造函数并将m_value 公开。)否则,唯一的解决方案是看到模板构造函数,它将其参数转发给T 构造函数:template &lt;typename ...P&gt; myProperty(P &amp;&amp;... p) : m_value(std::forward&lt;P&gt;(p)...) {}
  • @HolyBlackCat,即won't compile。为了让它工作,你必须加上额外的括号,比如m_value({std::forward&lt;P&gt;(p)...}),这样vp2 case 会编译,但vp1 不会。并且将myProperty 设为聚合不是一种选择。
  • 糟糕。它应该是: m_value{std::forward&lt;P&gt;(p)...},否则myProperty&lt;int&gt; ip{1}; 不会编译。 “vp2 case 会编译,但 vp1 不会” 这是否意味着你希望 vp1vp2 都编译,而不仅仅是 vp2
  • @HolyBlackCat,实际上不,我不希望 vp1 编译 :) 我的错,没有彻底测试你的建议,假设像 vp4{myVec3Objet} 这样的东西也不会编译,但它确实,所以这看起来不错:) 如果您发布它会接受答案。
  • 原来它破坏了复制构造(因为如果参数是非常量,它比 myProperty(const myProperty &amp;) 更好的匹配)。如果我弄清楚如何以一种简洁的方式解决这个问题,我会发布一个答案。

标签: c++ class templates constructor c++14


【解决方案1】:

一个简单的解决方案是使用可变参数模板构造函数:

template <typename ...P> myProperty(P &&... p) : m_value{std::forward<P>(p)...} {}

它使myProperty&lt;Vec3&gt; vp2{1, 2, 3}; 编译。

它还会阻止 myProperty&lt;Vec3&gt; vp1{{1, 2, 3}}; 编译(这似乎符合您的意图)。

此选项的问题在于它会阻止复制构造正常工作。
(如果参数是一个非常量myProperty&lt;T&gt;左值,那么这个可变参数构造函数比myProperty(const myProperty &amp;)匹配更好。)

这可以通过 SFINAE 解决:

C++17 与 &lt;experimental/type_traits&gt;:

#include <experimental/type_traits>
#include <utility>

template <typename T, typename ...P> using list_constructible = decltype(T{std::declval<P>()...});

// ...

template
<
    typename ...P,
    typename = std::enable_if_t<std::experimental::is_detected_v<list_constructible, T, P...>>
>
myProperty(P &&... p) : m_value{std::forward<P>(p)...} {}

C++14:

#include <type_traits>
#include <utility>

template <typename...> using void_t = void;
template <typename DummyVoid, template <typename...> class A, typename ...B> struct is_detected : std::false_type {};
template <template <typename...> class A, typename ...B> struct is_detected<void_t<A<B...>>, A, B...> : std::true_type {};
template <typename T, typename ...P> using list_constructible = decltype(T{std::declval<P>()...});

// ...

template
<
    typename ...P,
    typename = std::enable_if_t<is_detected<void, list_constructible, T, P...>::value>
>
myProperty(P &&... p) : m_value{std::forward<P>(p)...} {}

【讨论】:

  • 然而只是一个赞成票,有一些时间来试验这个解决方案,并保持对替代解决方案的开放。
猜你喜欢
  • 1970-01-01
  • 2015-01-22
  • 2016-06-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-03-24
  • 1970-01-01
相关资源
最近更新 更多