这个问题建立在 @FredOverflow's question .
CLARIFICATION:
initializer_list
approach is required as the VC++2012 has a bug the prevents forwarded expansion of namespaced arguments._MSC_VER <= 1700
has the bug.
我编写了一个可变参数模板函数,它可以折叠类型化容器中的任意数量的参数。我使用类型的构造函数将可变参数转换为可使用的值。例如。 _variant_t
:)
我的 MySql
需要这个一次性将参数推送到准备好的语句时的 C++ 库,而我的 MySqlVariant
将输入数据转换为 MYSQL_BIND
s。我可能会与 BLOB
合作s,我想尽可能避免复制构造move&&
周围的大容器。
我做了一个简单的测试,发现 initialize_list
copy-construct
用于存储的元素,并在超出范围时销毁它们。完美...然后我尝试将数据移出 initializer_list
而且,令我惊讶的是,它使用了lvalues
不是 rvalues
正如我所期望的 std::move
.
Funny as this happens just after Going Native 2013 clearly warned me that move does not move, forward does not forward... be like water, my friend - to stay on the deep end of thinking.
但这并没有阻止我 :) 我决定 const_cast
initializer_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 :)
我还上传了完整的测试 on IDEone ^显示为 std::string
的内存使用此功能正确移动。 (我会把它全部粘贴在这里,但它有点长......)
只要 _variant_t
(或任何最终包装对象) 具有正确的构造函数,这很棒。如果数据可以移出,那就更好了。在我对其进行测试时,它几乎可以正常工作std::move
在正确的方向:)
我的问题很简单:
- 我在标准方面做得对吗?
- 它工作正常的事实是有意的还是只是副作用?
- 如果
std::move
在initializer_list
上默认不工作,我在这里做什么:非法、不道德、骇人听闻......还是完全错误?
PS:我是自学成才的 Windows Native C++
开发者,对标准一无所知。
^ 如果我在这里做非常不标准的事情,我的借口。
更新
谢谢大家,我现在已经有了答案和解决方案(一个短的一个长的)。
And I love the C++11 side of SO. Many knowledgeable people here...
最佳答案
不幸的是,在一般情况下,这是未定义的行为。在§8.5.4/5,强调我的:
An object of type
std::initializer_list<E>
is constructed from an initializer list as if the implementation allocated a temporary array ofN
elements of typeconst E
, whereN
is the number of elements in the initializer list. Each element of that array is copy-initialized with the corresponding element of the initializer list, and thestd::initializer_list<E>
object is constructed to refer to that array.
您在哪里看到 std::initializer_list<E>
, 你可以把它当作 const E[N]
.
所以当你 const_cast
离开const
,您正在查看对 const
的可变引用目的。对 const
的任何修改对象是未定义的行为。
当你移动它时 std::string
,您正在修改 const
目的。不幸的是,未定义行为的一种行为是看似正确的行为。但这在技术上是未定义的。
请注意,当您使用 std::move(int)
换成另一个,那个是明确定义的,因为int
的只能被复制,所以移动什么也不做,也没有 const
对象被修改。但总的来说,它是未定义的。
关于c++ - 将 const_cast 元素移出 std::initializer_list 是否有风险?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/18686991/