【问题标题】:type inference for std::initializer_liststd::initializer_list 的类型推断
【发布时间】:2014-05-08 00:49:49
【问题描述】:

如果我写这个

std::vector<std::string> v{"one","two","three"};

关联的std::initializer_list 模板推断的类型是什么? 换句话说,当char * 字符串文字转换为std::string 时?

最好将其声明为

std::vector<std::string> v{std::string("one"),
                           std::string("two"),
                           std::string("three")};

为了避免与所涉及模板的类型推断机制相关的问题? 我会保持同样的优化吗?

【问题讨论】:

  • 你想解决什么问题?
  • @LightnessRacesinOrbit 没什么特别的,我试图了解 C++ 类型系统在这种特殊情况下是如何工作的。
  • 好的。我警告你,这是一条危险的道路;)
  • @LightnessRacesinOrbit 我希望你能详细说明
  • C++ 对这类事情的规则可能非常复杂。

标签: c++ templates c++11 type-inference initializer-list


【解决方案1】:

来自http://en.cppreference.com/w/cpp/language/string_literal

无前缀字符串字面量的类型是const char[]

事情就是这样发展的:

#include <iostream>
#include <initializer_list>
#include <vector>
#include <typeinfo>
#include <type_traits>
using namespace std;

int main() {
    std::cout << std::boolalpha;
    std::initializer_list<char*> v = {"one","two","three"}; // Takes string literal pointers (char*)
    auto var = v.begin();
    char *myvar;
    cout << (typeid(decltype(*var)) == typeid(decltype(myvar))); // true

    std::string ea = "hello";
    std::initializer_list<std::string> v2 = {"one","two","three"}; // Constructs 3 std::string objects
    auto var2 = v2.begin();
    cout << (typeid(decltype(*var2)) == typeid(decltype(ea))); // true
    std::vector<std::string> vec(v2);
    return 0;
}

http://ideone.com/UJ4a0i

【讨论】:

    【解决方案2】:

    更新:回答您关于类型推理的问题: vector&lt;string&gt; 的初始化列表构造函数采用 initializer_list&lt;string&gt;。它不是模板化的,因此在类型推断方面没有任何反应。

    不过,这里应用的类型 conversion 和重载解析规则还是很有趣的,所以我会保留我最初的答案,因为您已经接受了它:

    原答案:

    首先,编译器只看到initializer list{"one","two","three"},它只是一个初始化器列表,还不是std::initializer_list类型的对象。

    然后它会尝试找到一个合适的 vector&lt;string&gt; 构造函数来匹配该列表。 这是一个有点复杂的过程,如果您对确切的过程感兴趣,最好在标准本身中查找。

    因此,编译器决定从初始化程序列表中创建std::initializer_list&lt;string&gt; 的实际对象,因为从char*std::strings 的隐式转换使这成为可能。

    另一个,也许更有趣的例子:

    std::vector<long>   vl1{3};
    std::vector<string> vs1{3};
    std::vector<string> vs2{0};
    

    这些有什么作用?

    1. 第一行比较简单。 初始化器列表 {3} 可以转换为 std::initializer_list&lt;long&gt; 类似于上面的 {"onm", "two", "three"} 示例,因此您会得到一个具有单个元素的向量,其值为 3。

    2. 第二行不同。它构造了一个由 3 个空字符串组成的向量。为什么?因为 初始化列表 {3} 决不能转换为 std::initializer_list&lt;string&gt;,所以“普通”构造函数 std::vector&lt;T&gt;::vector(size_t, T = T()) 会启动并给出三个默认构造的字符串。

    3. 这个应该和第二个差不多吧?它应该给出一个空向量,换句话说,默认构造的字符串为零。 错了!0 可以被视为空指针常量,并验证 std::initializer_list&lt;string&gt;。只有这一次该列表中的单个字符串由空指针构造,这是不允许的,因此您会遇到异常。

    【讨论】:

    • 我对这个答案并不满意,std::vectorstd::initializer_list 采用相同的 T 的事实证明了 "one" 是有效输入这一事实,因为其中一个std::string 的构造函数采用 char *。并未真正解释类型推断的工作原理以及 C++11 允许进行哪些优化。
    • @user2485710 如果这是你的问题,那么你的例子是不相关的。此外,这个问题也不适合 SO,因为它过于宽泛。
    • @user2485710 我试图解释更多发生的事情 - 并说出一个关键、有趣的部分。因为它只对语言律师来说过于乏味和有趣;-)
    • 坦率地说,即使对于一些语言律师,即我,它仍然非常无趣。 ;)
    • 我在示例中使用std::stringchar * 的原因是这种特殊情况意味着类型之间的转换,尤其是指针和C++ 对象之间的转换。就我的实验而言,C++11 中的类型推断并不能很好地处理指针,例如 auto 揭示了使用指针时的一些弱点。这里我们有一些优化空间(这意味着 C++ 需要以某种方式在某处进行切割)和代表用户想要构建的相同“事物”的 2 种类型。
    【解决方案3】:

    没有类型推断,因为 vector 只提供了一个带有初始化列表的完全专用的构造函数。我们可以添加一个模板间接来玩类型推导。下面的示例表明 std::initializer_list&lt;const char*&gt; 是向量构造函数的无效参数。

    #include <string>
    #include <vector>
    
    std::string operator"" _s( const char* s, size_t sz ) { return {s, s+sz}; }
    
    template<typename T>
    std::vector<std::string> make_vector( std::initializer_list<T> il ) {
        return {il};
    }
    
    int main() {
        auto compile = make_vector<std::string>( { "uie","uieui","ueueuieuie" } ); 
        auto compile_too = make_vector<std::string>( { "uie"_s, "uieui", "ueueuieuie" } ); 
        //auto do_not_compile = make_vector( { "uie","uieui","ueueuieuie" } ); 
    }
    

    Live demo

    【讨论】:

      猜你喜欢
      • 2015-08-02
      • 1970-01-01
      • 1970-01-01
      • 2021-09-20
      • 1970-01-01
      • 2012-09-08
      相关资源
      最近更新 更多