【问题标题】:c++ variadic template constructor and common constructorsc++可变参数模板构造函数和常用构造函数
【发布时间】:2016-01-02 15:42:21
【问题描述】:

类似 (c++14) 的代码:

struct S { int a; int b; };

class C
{
  public:
    C(char const*, size_t) {} // 1
    C(S const&) {} // 2
    C(S const*) {} // 3
    template<typename ...T> C(T&& ...) {} // 4

 // C(S) {} // 5
 // C(S*) {} // 6
};

S s { 1, 2 };
C c1 { s }; // calls 4 and not 2
C c2 { "abc", 3 }; // calls 4 and not 1
C c3 { (char const*)"abc", (size_t)3 }; // calls 1 - ok
C c4 { s }; // calls 5 if uncommented
C c5 { &s }; // calls 6 if uncommented
S const s2 {};
C c6 { &s2 }; // calls 3

如果简单构造函数与传递的参数具有完全相同的签名,则调用它。 是否有一些技巧可以像往常一样使用带有可变参数模板构造函数的通用构造函数,而不复制类,作为参数传递,以及重载构造函数,例如:

C(S const*) {}
C(S*) {}

并且在构造函数中没有额外的标签

【问题讨论】:

  • 您希望保留可变参数模板化和非模板化构造函数,并确保它首先调用非模板化构造函数;这是正确的吗?
  • 你呢?没用,这是故意的吗?
  • 问题是具有前向(通用)引用的构造函数“吃掉”任何东西,它们优于所有其他构造函数。
  • @Lorenzo Belli U 只是表明 C 是模板。可能是语义来解决问题
  • @РоманКоптев 但是C 在您的任何示例中都不是模板。

标签: c++ templates constructor c++14 variadic-templates


【解决方案1】:

创建两层构造函数。然后标记调度。

template<template<class...>class Z, class T>
struct is_template:std::false_type{};
template<template<class...>class Z, class...Ts>
struct is_template<Z, Z<Ts...>>:std::true_type{};

struct foo {
private:
  template<class T> struct tag{ explicit tag(int) {} };
public:
  foo( tag<std::true_type>, const char*, size_t );
  template<class...Ts>
  foo( tag<std::false_type>, Ts&&...ts );

public:
  foo() = default; // or whatever
  template<class T0, class...Ts,
    std::enable_if_t<!is_template<tag, std::decay_t<T0>>{},int> =0>
  foo(T0&&t0, Ts&&...ts):
    foo( tag<typename std::is_constructible< foo, tag<std::true_type>, T0&&, Ts&&... >::type>{0}, std::forward<T0>(t0), std::forward<Ts>(ts)... )
  {}
};

“首选”ctors 以std::true_type 为前缀,“不那么首选”ctors 以std::false_type 为前缀。

这具有完美转发的常见缺陷。例如,如果您采用初始化器列表,您将需要另一个明确采用它的“公共”ctor。并且函数名参数神奇的重载将不起作用。 NULLint。等等。

您可以想象一个版本,它没有两层,而是有一个任意数字。面向公众的 ctor 中的 is_constructible&lt; ... &gt; 子句被替换为找到最高 N 的一些魔法,这样 tag&lt;N&gt;, blah... 可以构造对象(或者,最低的 N,无论你想做什么)。然后它返回类型tag&lt;N&gt;,然后分派到该层。

使用这样的技术:

template <typename... T,
      typename = std::enable_if_t<!std::is_constructible<C, T&&...>::value>
       >
C(T&&... ) { }

在路上遇到a serious problem,因为我们在它得到错误答案的上下文中实例化了is_constructible。在实践中,编译器会缓存模板实例化的结果,所以现在is_constructible 的结果取决于编译器顺序(我怀疑违反了 ODR)。

【讨论】:

  • 不应该is_constructible 接受&lt;foo, true_type, T0&amp;&amp;, Ts&amp;&amp;...&gt; 吗?顺便说一句,这太棒了。
  • is_constructible 应该在不相关的上下文中执行访问检查(即,没有私有 ctor)。
  • @T.C.固定的。使它们成为伪私有,需要一个私有模板才能调用。
  • @barry 我想防止{} 能够构造tag。现在调用者必须tag&lt;blah&gt;{0},这不能在类之外完成,因为他们不能命名类型tag&lt;blah&gt;
  • @TobiasHermann 应该叫is_instance_of_template。当且仅当第二个参数是第一个参数模板的实例时,它才是真的。这里我用它来防止公共完美转发ctor吃掉tag&lt;?&gt;-tagged构造函数调用。
【解决方案2】:

当且仅当这些参数不允许您使用 std::is_constructible 以其他方式构造 C 时,您才可以启用可变参数构造函数。

即:

template <typename... T,
          typename = std::enable_if_t<!std::is_constructible<C, T&&...>::value>
           >
C(T&&... ) { }

随着这一变化,C c1{s} 会调用(2)C c2{"abc", 3} 会调用(1),而C c7{1, 2, 3, 4, 5} 会调用(4)

【讨论】:

  • 我有点困惑:为什么这不会导致无限递归模板实例化?
  • @dyp 我相信这个构造函数在其声明符结束之前不会被考虑。
  • 请参阅melpon.org/wandbox/permlink/JM2U8pxxvguewhd1 对于最后一个int 参数,可变参数ctor 应该产生更好的重载。当 SFINAE 被停用时,它确实被调用。所以在这个is_constructible检查中似乎考虑了后来的ctors。
  • int x = 0; C c{x}; std::cout &lt;&lt; std::is_constructible&lt;C, int&amp;&gt;{} &lt;&lt; '\n';oopsWhat goes wrong
【解决方案3】:

这是另一种不使用标签调度的解决方案。在这里,构造函数按选定的顺序进行测试,并且可以使用带有可变参数模板的简单构造函数,而无需从接口可变参数模板构造函数完美转发。例如。我可以使用带有 std::pair 参数的构造函数,并在其中使用大括号初始化列表 { "String", 6 } ,而不是显式传递 std::pair&lt;char const*, size_t&gt;("String", 6) 等等,如示例所示。该方法不仅可以控制构造函数的优先级,还可以控制其他成员重载的优先级。目前它需要一个辅助类。

我是不同元编程技巧的新手,这只是一个建议。我不会改进它。

#include <type_traits>
#include <cstddef>
#include <iostream>
using namespace std;

一些类似标准的类:

struct null_helper { static constexpr int const value = 0; };

template<template<unsigned> typename Helper, unsigned order, typename ...TT>
class constructible_from_order
{
    using PrevType = typename conditional<(order < 1), null_helper,
                                           constructible_from_order<Helper, order-1, TT&&...>>::type;
    static constexpr int const prev = PrevType::value;
    static constexpr bool const is_this_constructible = is_constructible<Helper<order>, TT&&...>::value;
  public:
    static constexpr int const value = prev ? prev : is_this_constructible ? order : 0;

}; // template class constructible_from_order

template<template<unsigned> typename Helper, unsigned order, typename ...TT>
using enable_in_order = enable_if<(constructible_from_order<Helper, order, TT&&...>::value == order)>;

template<template<unsigned> typename Helper, unsigned order, typename ...TT>
using enable_in_order_t = typename enable_in_order<Helper, order, TT&&...>::type;

类定义举例:

using blob_data = pair<char const*, size_t>;

class C {

    template<unsigned order>
    class helper
    {
      public:
        helper(char const*, size_t) {} // 1

        helper(blob_data const&, blob_data const&) {} // 1

        template<typename T, typename = enable_if_t<(order == 2) && sizeof(T)>>
        helper(blob_data const&, T&&) {} // 2

        template<typename T, typename = enable_if_t<(order == 3) && sizeof(T)>>
        helper(T&&, blob_data const&) {} // 3

        template <class... Ts, typename = enable_if_t<(order == 4) && sizeof...(Ts)>>
        helper(Ts&&... ) {} // 4

    }; // template class helper

  public: // constructors:

    // order 1
    C(char const*, size_t) { cout << "1" << endl; }

    // order 1
    C(blob_data const&, blob_data const&) { cout << "1" << endl; }

    // order 2
    template<typename T, typename = enable_in_order_t<helper, 2, blob_data const&, T&&>>
    C(blob_data const&, T&&) { cout << "2" << endl; }

    // order 3
    template<typename T, typename = enable_in_order_t<helper, 3, T&&, blob_data const&>>
    C(T&&, blob_data const&) { cout << "3" << endl; }

    // order 4
    template <class... Ts, typename = enable_in_order_t<helper, 4, Ts&&... >>
    C(Ts&&... ) { cout << "4" << endl;}

  public: // member functions:

    // order 1
    void fun(char const*, size_t) { cout << "1" << endl; }

    // order 1
    void fun(blob_data const&, blob_data const&) { cout << "1" << endl; }

    // order 2
    template<typename T, typename = enable_in_order_t<helper, 2, blob_data const&, T&&>>
    void fun(blob_data const&, T&&) { cout << "2" << endl; }

    // order 3
    template<typename T, typename = enable_in_order_t<helper, 3, T&&, blob_data const&>>
    void fun(T&&, blob_data const&) { cout << "3" << endl; }

    // order 4
    template <class... Ts, typename = enable_in_order_t<helper, 4, Ts&&... >>
    void fun(Ts&&... ) { cout << "4" << endl;}

}; // class C

用作:

int main() {

  char const* str = "aaa";

  // constructors:
  cout << "Constructors: " << endl;
  cout << "1: "; C  c1   {        str,  size_t{5} };
  cout << "1: "; C  cx   { { str, 5 }, { str, 5 } };
  cout << "2: "; C  c2   { { str, 5 },        str };
  cout << "3: "; C  c3   {        str, { str, 5 } };
  cout << "4: "; C  c4   {        str,        str };
  cout << endl;

  // functions:
  cout << "Functions: " << endl;
  cout << "1: "; c1.fun(        str,  size_t{5} );
  cout << "1: "; c1.fun( { str, 5 }, { str, 5 } );
  cout << "2: "; c1.fun( { str, 5 },        str );
  cout << "3: "; c1.fun(        str, { str, 5 } );
  cout << "4: "; c1.fun(        str,        str );
  cout << endl;

} // main

程序输出:

Constructors:
1: 1
1: 1
2: 2
3: 3
4: 4

Functions: 
1: 1
1: 1
2: 2
3: 3
4: 4

【讨论】:

    猜你喜欢
    • 2016-09-02
    • 2018-05-18
    • 1970-01-01
    • 2015-05-06
    • 2018-02-03
    • 1970-01-01
    • 2011-09-05
    • 1970-01-01
    • 2016-09-12
    相关资源
    最近更新 更多