14

这个问题建立在这个@FredOverflow 的问题之上。

澄清initializer_list方法是必需的,因为VC++2012 有一个错误,可以防止命名空间参数的转发扩展。_MSC_VER <= 1700有错误。

我编写了一个可变参数模板函数,它可以折叠类型化容器中的任意数量的参数。我使用类型的构造函数将可变参数转换为可使用的值。例如_variant_t:)

在一次将参数推送到准备好的语句时,我的MySqlC++ 库需要这个,而我MySqlVariant将输入数据转换为MYSQL_BINDs。因为我可能使用s,所以当我可以使用大型容器BLOB时,我想尽可能避免复制构造。move&&

initialize_list我已经做了一个简单的测试,并注意到copy-construct存储的元素会在超出范围时销毁它们。完美...然后我尝试将数据移出,令我initializer_list惊讶的是,它与.lvaluesrvaluesstd::move

有趣的是,这发生在Going Native 2013明确警告我移动不动,前进不前进......就像水一样,我的朋友- 留在思想的深处。

但这并没有阻止我 :)我决定价值观并仍然const_castinitializer_list它们移出。需要执行驱逐令。这是我的实现

template <typename Output_t, typename ...Input_t>
inline Output_t& Compact(Output_t& aOutput, Input_t&& ...aInput){
    // should I do this? makes sense...
    if(!sizeof...(aInput)){
        return aOutput;
    }

    // I like typedefs as they shorten the code :)
    typedef Output_t::value_type Type_t;

    // can be either lvalues or rvalues in the initializer_list when it's populated.
    std::initializer_list<Type_t> vInput = { std::forward<Input_t>(aInput)... };

    // now move the initializer_list into the vector.
    aOutput.reserve(aOutput.size() + vInput.size());
    for(auto vIter(vInput.begin()), vEnd(vInput.end()); vIter != vEnd; ++vIter){
        // move (don't copy) out the lvalue or rvalue out of the initializer_list.
        // aOutput.emplace_back(std::move(const_cast<Type_t&>(*vIter))); // <- BAD!
        // the answer points out that the above is undefined so, use the below
        aOutput.emplace_back(*vIter); // <- THIS is STANDARD LEGAL (copy ctor)!
    }

    // done! :)
    return aOutput;
}

使用它很容易:

// You need to pre-declare the container as you could use a vector or a list...
// as long as .emplace_back is on duty!
std::vector<MySqlVariant> vParams;
Compact(vParams, 1, 1.5, 1.6F, "string", L"wstring",
    std::move(aBlob), aSystemTime); // MySql params :)

我还在 IDEone 上上传了一个完整的测试 ^,它显示为std::string使用此功能正确移动的内存。(我会把它全部粘贴在这里,但它有点长......)

只要_variant_t (或任何最终包装对象)具有正确的构造函数,那就太好了。如果数据可以移出,那就更好了。当我测试它并且事情朝着正确的方向发展时,它几乎可以工作std::move:)

我的问题很简单:

  • 我在标准方面做得对吗?
  • 它工作正常的事实是有意的还是只是副作用?
  • 如果std::move默认情况下不起作用initializer_list,那么我在这里做什么:非法,不道德,hacky ......还是完全错误

PS:我是一名自学成才的Windows Native C++开发人员,对标准一无所知。
^ 如果我在这里做非常不标准的事情,我的借口。

更新

谢谢大家,我现在既有答案也有解决方案(一个短的和长的)

我喜欢 SO 的 C++11 方面。 这里有很多知识渊博的人...

4

3 回答 3

9

不幸的是,在一般情况下,这是未定义的行为。在§8.5.4/5,强调我的:

type 的对象std::initializer_list<E>是从初始化列表构造的,就好像实现分配了一个临时N元素数组 typeconst E,其中N是初始化列表中的元素数。该数组的每个元素都使用初始值设定项列表的相应元素进行复制初始化,并且std::initializer_list<E>构造对象以引用该数组。

在你看到 a 的地方std::initializer_list<E>,你可以表现得好像它是 a 一样const E[N]

因此,当您const_cast离开时const,您正在查看对const对象的可变引用。对对象的任何修改const都是未定义的行为。

当您移动它时std::string,您正在修改一个const对象。不幸的是,未定义行为的一种行为是看似正确的行为。但这在技术上是不确定的。

请注意,当您std::move(int)进入另一个时,是明确定义的,因为int ' 只能被复制,所以移动什么也不做,也没有const对象被修改。但总的来说,它是未定义的。

于 2013-09-08T18:26:45.173 回答
1

找到了一个替代解决方案,对于任何分享我的痛苦的人:

#if _MCS_VER <= 1700
// Use the code in the OP!
// VS 2012- stuff comes here.
#else
// VS 2013+ stuff comes here.
template <typename Output_t>
inline Output_t& Compact(Output_t& aOutput){
    return aOutput;
}

template <typename Output_t, typename First_t>
inline Output_t& Compact(Output_t& aOutput, const First_t& aFirst){
    aOutput.emplace_back(aFirst);
    return aOutput;
}

template <typename Output_t, typename First_t>
inline Output_t& Compact(Output_t& aOutput, First_t&& aFirst){
    aOutput.emplace_back(std::move(aFirst));
    return aOutput;
}

template <typename Output_t, typename First_t, typename ...Next_t>
inline Output_t& Compact(Output_t& aOutput, const First_t& aFirst, Next_t&& ...aNext){
    aOutput.emplace_back(aFirst);
    return Compact(aOutput, std::forward<Next_t>(aNext)...);
}

template <typename Output_t, typename First_t, typename ...Next_t>
inline Output_t& Compact(Output_t& aOutput, First_t&& aFirst, Next_t&& ...aNext){
    aOutput.emplace_back(std::move(aFirst));
    return Compact(aOutput, std::forward<Next_t>(aNext)...);
}
#endif // _MCS_VER <= 1700

PSVC++2012 CTPnov2012 有一个 BUG 会阻止它在命名空间类上工作。所以,最初的解决方案没有const_cast必须做的。我所有的代码都是命名空间的。VC2013理论上已经解决了这个问题……所以我升级的时候会切换代码。

于 2013-09-08T19:06:38.097 回答
1

您可以将专精减一。这种“通用引用”特化也应该涵盖左值引用,在这种情况下std::move什么都不做。

template <typename Output_t, typename First_t>
inline Output_t& Compact(Output_t& aOutput, First_t&& aFirst){
    aOutput.emplace_back(std::forward<First_t>(aFirst));
    return aOutput;
}

资料来源:Scott Meyers 在 GoingNative2013 上的演讲;这篇文章中详细介绍了

于 2013-09-09T04:56:10.693 回答