【问题标题】:Conditionally enable a constructor whenever a member variable can be constructed by variadic arguments每当成员变量可以由可变参数构造时,有条件地启用构造函数
【发布时间】:2016-08-19 03:04:36
【问题描述】:

我有一个带有模板参数Tuple 的类foo,我想提供一个可变参数构造函数来初始化Tuple 类型的成员变量m_elements,只要定义了表达式m_elements{ static_cast<typename Tuple::value_type>(std::forward<Elements>(elements))... }

我们可以通过以下方式做到这一点:

template<class Tuple>
struct foo
{
    using value_type = typename Tuple::value_type;

    template<class... Elements, class U = Tuple,
        class = decltype(U{ static_cast<value_type>(std::declval<Elements>())... })>
    foo(Elements&&... elements)
        : m_elements{ static_cast<value_type>(std::forward<Elements>(elements))... }
    {}

    Tuple m_elements;
};

现在,这个构造函数是否启用也应该取决于其他一些条件。所以,我需要写一些类似的东西

template<class... Elements, class U = Tuple,
    class = std::enable_if_t</* some other conditions depending on U */>,
    class = decltype(U{ static_cast<value_type>(std::declval<Elements>())... })>

我想根据std::is_constructible 检查我的第一个条件,以便我可以将此检查移至enable_if。这可能吗?我尝试使用std::is_constructible_v&lt;U, decltype(static_cast&lt;value_type&gt;(std::declval&lt;Elements&gt;()))...&gt;,但这似乎并不等同于之前的检查。

例如,foo&lt;bar&lt;3&gt;&gt;{1, 2, 3};

template<std::size_t N>
struct bar
{
    using value_type = double;
    double data[N];
};

将使用前一次检查进行编译,但使用新检查会产生错误。

【问题讨论】:

  • 具有讽刺意味的是,fooTuple = std::tuple 时不起作用,因为std::tuple 没有value_type 属性,所以你没有真正工作这里有元组(在异构编译时集合的意义上)。
  • @Tim 这并不是那么讽刺。我真正的 foo 在数学库中,数学家通常将向量定义为 n 元组。
  • 啊,种元组。这更有意义。

标签: c++ templates c++14 typetraits


【解决方案1】:

正如Rostislav 提到的,如果T 不是函数类型,则std::is_constructible_v&lt;T, Args&gt;true,如果变量定义T obj(std::declval&lt;Args&gt;()...); 是格式良好的。 bar&lt;1&gt; obj(0.); 不是这样,因为bar&lt;1&gt; 没有对应的构造函数。

相比之下,bar1&lt;1&gt; obj{ 0. }; 格式正确。使用建议的Detection Toolkit,我们可以使用

template<class T, typename... Arguments>
using initializable_t = decltype(T{ std::declval<Arguments>()... });

template<class T, typename... Arguments>
constexpr bool is_initializable_v = is_detected_v<initializable_t, T, Arguments...>;

并将检查更改为

template<class... Elements, class U = Tuple,
    class = std::enable_if_t<is_initializable_v<U, decltype(static_cast<value_type>(std::declval<Elements>()))...>>>

我认为这比普通的 decltype 方法更具可读性。

【讨论】:

    【解决方案2】:

    is_constructible trait 表现不同的原因是确实没有 bar 的构造函数将三个 int 值作为参数。在这种特殊情况下,使用聚合初始化。

    但您可以通过使用简单的帮助程序 struct 将测试放入 enable_if

    #include <type_traits>
    
    template<typename T>
    struct test : std::true_type
    {
    };
    
    template<class Tuple>
    struct foo
    {
        using value_type = typename Tuple::value_type;
    
        template<class... Elements, class U = Tuple,
            class = std::enable_if_t<
                test<decltype(U{ static_cast<value_type>(std::declval<Elements>())... })>::value>
                /* && your other tests */
                >
        foo(Elements&&... elements)
            : m_elements{ static_cast<value_type>(std::forward<Elements>(elements))... }
        {}
    
        Tuple m_elements;
    };
    
    
    template<std::size_t N>
    struct bar
    {
        using value_type = double;
        double data[N];
    };
    
    int main()
    {
        foo<bar<3>>{1, 2, 3};
    }
    

    LIVE

    或者,您可以使用this 问题的任何答案创建类型特征,从而将decltype 魔法推到一个单独的位置。

    【讨论】:

    • 您的enable_if 的情况毫无意义。你将如何禁用构造函数?
    • @PiotrSkotnicki 我看不出禁用构造函数有什么令人惊讶的。它们也会受到函数重载的影响。
    • @Quentin 然后显示一个可以在这种test 失败时选择的构造函数,即,如果不能使用U{elements...} 将使用的构造函数
    • @PiotrSkotnicki 哦,你的意思是在这种特殊情况下。确实,这里只有一个构造函数。
    • 准确地说,我的意思是,将decltype 放在test 中,只是为了利用enable_if 的语法是没有意义的,这里仍然失败的是decltype 本身,而不是@ 987654337@。也就是说,您将无法添加此构造函数的另一个重载,如果 testfalse (无论这里是什么意思),则可以启用该重载
    猜你喜欢
    • 2011-08-08
    • 2013-11-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-11-02
    • 2016-01-02
    • 1970-01-01
    相关资源
    最近更新 更多