将const_cast元素移出std :: initializer_list会有任何风险吗?

Cod*_*gry 12 c++ c++11

这个问题建立在这个@ FredOverflow的问题上.

澄清:initializer_list需要方法,因为VC++ 2012有一个错误,阻止了命名空间参数的转发扩展._MSC_VER <= 1700有错误.

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

MySql在一次打击MySqlVariant中将参数推送到预准备语句时,我需要这个用于我的C++库,而我将输入数据转换为MYSQL_BINDs.因为我可以使用BLOBs,所以当我可以move&&使用大容器时,我希望尽可能避免复制构造.

我做了一个简单的测试,发现了initialize_list确实copy-construct为存储的元素,当它超出范围破坏它们.完美...然后我试图将数据移出,initializer_list并且令我惊讶的是,它并lvalues没有rvalues像我预期的那样使用std::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;
}
Run Code Online (Sandbox Code Playgroud)

使用它很容易:

// 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 :)
Run Code Online (Sandbox Code Playgroud)

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

只要_variant_t (或任何最终的包装对象)具有正确的构造函数,它就很棒.如果数据可以移出,那就更好了.它几乎可以工作,因为我测试它和std::move正确方向的东西:)

我的问题很简单:

  • 我是按标准做的吗?
  • 事实上它是正确的还是只是副作用?
  • 如果std::move默认不起作用initializer_list,我在这里做的是:非法的,不道德的,hacky ......或者只是简单的错误

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

UPDATE

谢谢大家,我现在有答案和解决方案(短期和长期).

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

GMa*_*ckG 8

在一般情况下,遗憾的是,这是未定义的行为.在§8.5.4/ 5,强调我的:

类型的对象std::initializer_list<E>是从初始化列表构造的,就好像实现分配了一个临时N的类型元素数组const E,其中N是初始化列表中元素的数量.使用初始化列表的相应元素对该数组的每个元素进行复制初始化,并std::initializer_list<E>构造该对象以引用该数组.

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

所以,当你const_cast离开时const,你正在看一个const对象的可变引用.对const对象的任何修改都是未定义的行为.

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

请注意,当您std::move(int)进入另一个时,是明确定义的,因为int 只能复制,因此移动不执行任何操作,也不会const修改任何对象.但总的来说,它是未定义的.

  • @CodeAngry:你的编译器是否喜欢它并不重要.标准说它调用了未定义的行为.例如,如果`const`-object被放入只读内存中,它可能会崩溃. (3认同)
  • @Yakk在下一段中:"数组与其他任何临时对象具有相同的生命周期,除了从数组初始化`initializer_list`对象扩展了数组的生命周期,就像绑定对临时对象的引用一样." (2认同)