Variadic模板问题

时间:2017-06-10 15:56:11

标签: c++ c++11 templates variadic-templates

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

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

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)}... {}

如果你有耐心,请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
}

2 个答案:

答案 0 :(得分:1)

问题包括两部分

重载分辨率

对于具有转发引用的可变构造函数

template<typename... Args>
Struct(Args&&...);

给定任何左值参数,Args将推导为左值引用,rvalue参数右值引用。

对于要调用的另一个构造函数,可变参数构造函数必须没有比构造函数更好的转换序列。

您的代码只包含一个带有一个参数的构造函数,即复制构造函数

Struct(const Struct&);

左值const Struct将与复制构造函数const Struct&绑定,而左值或右值Struct将与可变参数构造函数Struct&绑定或分别为Struct&&

返回类型的值类别

glvalue表达式引用在return语句中的函数中声明的自动持续时间变量被视为xvalue,因此将首先绑定到Struct&&。如果失败,表达式将被视为左值并继续绑定到Struct&const Struct&

在这种情况下,Struct&&使用可变参数构造函数成功,但会导致错误。

如果提供了移动构造函数,则在重载解析后the move constructor will be selected会丢弃可变参数构造函数。

回应编辑

删除用户提供的复制构造函数将允许隐式声明的移动构造函数,这将使代码编译,遵循上述原因。

可变构造函数是两个构造函数的良好替代,除了在语义错误之外,它需要任意参数但需要一个固定的(在这种情况下为2)量的参数来初始化字段。如果它被用作复制构造函数,您将得到相同的编译错误:不匹配的参数包长度,同时扩展&#39;字段&#39;

正如Daniel Jour's answer中提到的,你应该把一些SFINAE放在可变构造函数中以减轻一些痛苦。

答案 1 :(得分:1)

你已成为我所知的"too perfect forwarding".

的受害者

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

  

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

这告诉我们这行代码

return f;

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

看看这个构造函数的作用,很明显这个构造函数不能复制或移动Struct。其预期用例是从其中一个参数构造每个字段。但错误消息显示只有{strong> {/ 1>}类型的单个参数。因为参数的扩展也扩展了Struct<foo<int>, bar<double> >&(其中有两个),你会得到第二个错误:

  

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

但是为什么它需要完美的转发构造函数而不是可用的复制构造函数?因为转发构造函数能够提供比复制构造函数(其签名为Fields)更好的候选者(实际上是完全匹配):Struct(Struct const &),根据引用组合规则{{1} }。这正是Struct(Struct & &&)中使用Struct(Struct &)所需要的:毕竟f 非const 左值。

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

return f;

但是如果您还添加了f,则需要编写总共六个构造函数来涵盖所有案例。 不好

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

Struct(Struct & s)
  : Struct(static_cast<Struct const &>(s))
{}

volatile检查传递给具有单个字段的结构的单个参数是否属于结构自己的类型:

template<typename T>
using decay_t = typename decay<T>::type;

template<
  typename...T,
  std::enable_if<
    (sizeof...(T) == sizeof...(Fields))
    && not_self_helper<Struct, std::tuple<decay_t<T>...>>::value
    >::type * = nullptr
  >
constexpr Struct(T &&...x)
  : Fields{static_cast<T&&>(x)}... {}

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