Variadic模板问题

Jim*_*imR 6 c++ templates variadic-templates c++11

我有以下代码,我正在努力适应我自己的目的.这是一个学习练习,让我尝试更新我的C++技能.据我所知,这最初是用Clang 3.1编写的.

我尝试使用3.1到4.0和GCC 4.9到7.1的Clang版本进行编译,结果非常相似.

These are the error messages from GCC 5.1

Error 1: In instantiation of 'constexpr Struct<Fields>::Struct(T&& ...) [with T = {Struct<foo<int>, bar<double> >&}; Fields = {foo<int>, bar<double>}]':
<source>:46:12:   required from here

Error 2: <source>:28:61: error: mismatched argument pack lengths while expanding 'Fields'
 constexpr Struct(T &&...x) : Fields{static_cast<T&&>(x)}... {}
Run Code Online (Sandbox Code Playgroud)

如果你有耐心,请ELI5:P

你可以在godbolts编译器doohickey中看到这个:https://godbolt.org/g/2dRPXf

编辑:

鉴于@ Passer-By和@ Daniel-Jour的答案,我想知道是否Struct(Struct const &) = default;有必要.将删除此构造函数Struct具有我不知道或无法预见的效果(我不是C++ Swami!)?

构造函数constexpr Struct(T &&...x) : Fields{static_cast<T&&>(x)}... {}是一个很好的替代品,否则会产生Struct(Struct const &) = default;什么?

到目前为止,我对这两个提议的解决方案都感到非常模糊.

结束编辑

// From http://duriansoftware.com/joe/Self-aware-struct-like-types-in-C++11.html
// edited a bit to stop the compiler whining about comments in multiline macros
#include <type_traits>
#include <utility>

#define SELFAWARE_IDENTIFIER(NAME) \
    template<typename T> \
    struct NAME { \
        T NAME; /* field name */ \
        constexpr static char const *name() { return #NAME; } /* field type */ \
        using type = T; /* field value generic accessor */ \
        T &value() & { return this->NAME; } \
        T const &value() const & { return this->NAME; } \
        T &&value() && { return this->NAME; } \
    };

template<typename...Fields>
struct Struct : Fields... {
    // A convenience alias for subclasses
    using struct_type = Struct;

    // Preserve default constructors
    Struct() = default;
    Struct(Struct const &) = default;

    // Forwarding elementwise constructor
    template<typename...T>
    constexpr Struct(T &&...x) : Fields{static_cast<T&&>(x)}... {} // Error 2 here
};

SELFAWARE_IDENTIFIER(foo)
SELFAWARE_IDENTIFIER(bar)
// Aliasing a Struct instance
using FooBar = Struct<foo<int>, bar<double> >;
// Inheriting a Struct instance (requires inheriting constructors)
struct FooBar2 : Struct<foo<int>, bar<double>> { using struct_type::struct_type; };

static_assert(std::is_trivial<FooBar>::value, "should be trivial");
static_assert(FooBar{2, 3.0}.foo + FooBar{2, 4.0}.foo == 4, "2 + 2 == 4");


FooBar frob(int x) {
    FooBar f = {x, 0.0};
    f.foo += 1;
    f.bar += 1.0;
    return f; // Error 1 here
}
Run Code Online (Sandbox Code Playgroud)

Dan*_*our 1

你已经成为我所知道的受害者“过于完美的转发”的受害者。

\n\n

要调试此问题,首先仔细查看错误消息:

\n\n
\n

constexpr Struct<Fields>::Struct(T&& ...)[的实例化T = {Struct<foo<int>, bar<double> >&}; Fields = {foo<int>, bar<double>}

\n
\n\n

这告诉我们这行代码

\n\n
return f;\n
Run Code Online (Sandbox Code Playgroud)\n\n

不会按预期调用复制或移动构造函数,而是调用完美的转发构造函数。

\n\n

看看这个构造函数的作用,很明显这个构造函数不能从Struct. 其预期的\xe2\x80\x8b 用例是根据参数之一构造每个字段。但错误消息显示只有一个类型为 的参数Struct<foo<int>, bar<double> >&。因为参数的展开也会展开Fields(其中有两个),所以你会得到第二个错误:

\n\n
\n

[..]错误:参数包长度不匹配[..]

\n
\n\n

但为什么它采用完美的转发构造函数而不是同样可用的复制构造函数呢?因为转发构造函数能够提供比复制构造函数(其签名为 )更好的候选者(实际上是精确匹配Struct(Struct const &)):Struct(Struct & &&),根据引用组合规则为Struct(Struct &)。这正是f中需要使用的return f;:毕竟f 一个非常量值。

\n\n

解决此问题的一种可能性是提供另一个具有确切签名的(复制)构造函数:

\n\n
Struct(Struct & s)\n  : Struct(static_cast<Struct const &>(s))\n{}\n
Run Code Online (Sandbox Code Playgroud)\n\n

但如果您还添加了内容,volatile则总共需要编写六个构造函数才能涵盖所有情况。不太好

\n\n

更好的解决方案是使用 SFINAE 将完美转发构造函数从重载决策中排除:

\n\n
template<typename T>\nusing decay_t = typename decay<T>::type;\n\ntemplate<\n  typename...T,\n  std::enable_if<\n    (sizeof...(T) == sizeof...(Fields))\n    && not_self_helper<Struct, std::tuple<decay_t<T>...>>::value\n    >::type * = nullptr\n  >\nconstexpr Struct(T &&...x)\n  : Fields{static_cast<T&&>(x)}... {}\n
Run Code Online (Sandbox Code Playgroud)\n\n

not_self_helper检查传递给具有单个字段的结构的单个参数是否属于结构本身的类型:

\n\n
template<typename, typename>\nstruct not_self_helper : std::true_type {};\n\ntemplate<typename Self>\nstruct not_self_helper<Self, std::tuple<Self>> : std::false_type {};\n
Run Code Online (Sandbox Code Playgroud)\n\n

这解决了您的主要问题:转发构造函数在语义上是错误的。它不接受任意数量的参数,但需要与结构体具有的字段数量完全相同的参数。此外,任何字段都不应该从包含结构本身构造(毕竟,递归成员资格意味着无限的结构大小)。不过,我缩短了该测试,仅检查何时存在单个参数。严格来说,这在语义上是错误的,但实际上它涵盖了最“危险”的情况:当您的转发构造函数被错误地选择用于复制/移动构造时。

\n