【问题标题】:Why std::get does not work with variables?为什么 std::get 不适用于变量?
【发布时间】:2019-05-30 19:12:51
【问题描述】:

我在理解函数,尤其是模板函数和局部变量在编译期间的行为方面遇到了麻烦。

所以这段代码与std::get 配合得很好:

enum class UserInfoFields{name, email, address};

using UserInfo = std::tuple<std::string, std::string, std::string>;

int main()
{
    UserInfo s{"Edmund", "edmund@page.me", "Denver street 19"};
    std::cout << std::get<static_cast<size_t>(UserInfoFields::name)>(s) << std::endl;
    return 0;
}

据我了解,这是因为std::get 是一个模板函数,它需要在编译期间知道模板参数。这是有道理的,因为static_cast&lt;... 在编译期间为我们提供了价值。

如果我把main()代码改成这样,我不明白的地方:

 int main()
{
    UserInfo s{"Edmund", "edmund@page.me", "Denver street 19"};
    auto a = static_cast<size_t>(UserInfoFields::name);
    std::cout << std::get<a>(s) << std::endl;
    return 0;
}

这是不允许的。我知道我必须使用constexpr,但我想知道,为什么第二个代码不起作用?

【问题讨论】:

    标签: c++ templates c++14


    【解决方案1】:

    你自己写的

    std::get 是一个模板函数,它需要在编译期间知道模板参数

    局部变量的值在编译期间是(在一般情况下)已知的;局部变量的值是运行时属性。因此,局部变量不能用作模板参数。

    如果你想把它当作一个来使用,你必须让它成为一个编译时的值。这是通过将其设为constexpr 来实现的(正如您在问题中所述)。

    【讨论】:

    • 你写下来后我觉得好傻...我不知道,但我的大脑拒绝接受在编译期间不知道局部变量,我什至没有去阅读因为我认为我是对的。
    • 我有一个问题,如果我采取一个简单的模板功能:template&lt;typename T&gt; auto to_size_t(T index) { return static_cast&lt;size_t&gt;(index); } 并写在主int f = 4; std::cout &lt;&lt; to_size_t(f); 为什么这样工作?如果 f 是局部变量
    • @aikhs 在这种to_size_t 的例子中,局部变量f 被用作函数 参数,这非常好。 模板 参数必须是编译时常量。如果你写了template &lt;int v&gt; auto to_size_t() { return static_cast&lt;size_t&gt;(v); },你也不能在那里使用f
    • 顺便说一句,const auto a = 0; std::cout &lt;&lt; std::get&lt;a&gt;(...) 也可以。
    • @Angew 好的,我明白了,这是因为模板参数应该指向或复制一些参数值,否则模板参数将在编译期间挂起?
    【解决方案2】:

    模板非类型参数,如std::get&lt;&gt; 采用的size_t,必须是编译时常量。

    您的auto a 不是编译时间常数。在您的具体情况下,您可以证明 a 在此时的值永远不会改变,并且始终是 0

    但是 C++ 是一种强类型语言,它依赖于程序员提供的显式类型。在评估std::get&lt;a&gt; 时,C++ 允许自己知道的关于a 的唯一事情是它是std::size_t 类型的非常量非constexpr 局部变量。

    因此,如果std::get&lt;a&gt; 有效,那么必须:

    int main(int argv, char ** argc) {
      UserInfo s{"Edmund", "edmund@page.me", "Denver street 19"};
      std::size_t a = argv; // number of arguments
      std::cout << std::get<a>(s) << std::endl;
    }
    

    std::get&lt;std::size_t&gt; 是一个nothrow 函数,不允许在运行时失败。如果您使用 100 个参数调用此代码,则上述代码可能无法工作。

    其次,虽然您的 UserInfo 是 3 种相同类型,但 std::get&lt;size_t&gt;(tuple&lt;a,b,c&gt;) 在类型不同时有效。所以

    using UserInfo = std::tuple<int, std::string, double>;
    

    那么std::get&lt; argv &gt;( some_user_info ) 也必须工作。在这种情况下,它返回的类型可以是三种类型中的任何一种——但 C++ 要求所有表达式都具有 一种 类型。

    简短的版本是“语言标准是这样说的”。较长的版本是“在一般情况下,您的代码不起作用”。

    现在,您可以通过一些更改来解决您的特定情况下的代码。

    using UserInfo = std::array<std::string, 3>;
    

    现在UserInfo 已知有 3 种统一类型。

    std::cout << s[a] << std::endl;
    

    现在你传入索引,因为[] 的参数不是模板非类型参数,它可以在运行时变化。

    [] 允许在索引超出范围时执行 UB。 (std::get&lt;a&gt; 不是)。

    现在,C++ 可能会发展,新标准可能会带来一些魔力,并以某种方式检测您的特殊情况并允许 std get 在运行时失败等。但是每次调用 std::get 都可能是运行时失败,而在它之前不是;您的应用程序的测试表面刚刚爆炸。

    哎呀,它可以自动检测到 auto a = blah 在上一行使用常量表达式进行了初始化,并使其在下一行中的使用自动成为常量表达式。

    但是,一个知道自己在做什么的程序员将调用get_proper_a() 替换为3 进行调试可能会使他们的代码虚假地改变行为,因为秘密类型信息“泄漏”到代码中。而当实际返回3(但编译器无法证明)的get_proper_a() 运行时,代码会在运行时中断。

    【讨论】:

    • 那么为什么有人要创建一个带有模板参数的模板函数呢?如果使用函数参数制作模板函数更容易?
    • @aikhs 函数返回值的静态类型不能依赖于函数参数值。它可以依赖于模板非类型模板参数值。
    【解决方案3】:

    这不是一个正式的解释,而是一个(希望)易于理解的理由。

    由于 C++ 是一种强类型语言,std::get 的返回类型必须在编译时让编译器知道。但是std::get 的不同版本会返回不同的类型——因为元组中不同索引处可能有不同的类型。

    因此,std::get 的模板参数的值需要来自编译器在编译时已知的某个地方。 constexpr 变量具有编译器已知的值,例如,const 变量用常量表达式初始化。但简单的非常量整数变量,即使在定义时初始化,也不是。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-09-21
      • 1970-01-01
      • 2015-04-21
      • 2014-08-22
      • 2012-12-23
      • 1970-01-01
      相关资源
      最近更新 更多