使用std::get<index>访问std::variant

3

如何使用v.index()std::get<index>(v)访问变量的成员?

当变量有多个相同类型的条目时非常有用。

以下代码无法正常工作。这段代码在GCC或clang上都无法编译:

#include <iostream>
#include <variant>
#include <string>
#include <sstream>

typedef std::variant<int, int, std::string> foo;

std::string bar(const foo f) {

    const std::size_t fi = f.index();
    auto ff = std::get<fi>(f);

    std::ostringstream ss;      
    ss << "Index:" << fi << "   Value: " << ff;
    return ss.str();
}


int main()
{
    foo f( 0 );

    std::cout << bar(f);
}

当然,std::get有许多版本,因此错误消息很长。
gcc抱怨(对于每个get< >版本)。
prog.cc:10:29: error: the value of 'fi' is not usable in a constant expression
     auto ff = std::get<fi>(f);
                             ^
prog.cc:9:23: note: 'fi' was not initialized with a constant expression
     const std::size_t fi = f.index();
                       ^~
prog.cc:10:29: note: in template argument for type 'long unsigned int'
     auto ff = std::get<fi>(f);

Clang会为每个版本的get<>(无论是_Tp还是_Ip)发出警告。

candidate template ignored: invalid explicitly-specified argument for template parameter '_Tp' 

Wandbox

更新为询问如何解决问题而非错误信息的含义。

涉及IT技术相关内容,建议您提供更具体的翻译需求以便我更好地进行翻译。


实际上,标准规定了std::size_t。 - KarlM
@md5i 0 是 variant 的有效索引;第1点(这个情况)并没有说明索引参数的类型。你可以使用 00L(size_t)0 等等。第2点讨论了 get<long>(f),这将是不合法的。 - M.M
那么也许我应该使用索引来进行 std::visit? - KarlM
请查看@Slava 这里的用例。 - M.M
@KarlM,你如何将香蕉的数量分配给此变量的一个实例? - Slava
显示剩余13条评论
2个回答

13

std::get<>适用于在编译时已知变体索引的情况。

如果您需要处理直到运行时才能确定类型的变体值,则惯用做法是使用具有std::visit的访问器。

#include <iostream>
#include <variant>
#include <string>

struct output_visitor
{
    template< typename T >
    void operator() ( const T& value ) const
    {
        std::cout << value;
    }   
};

int main()
{
    std::variant<int, std::string> f( 0 );

    std::visit( output_visitor{}, f );
}

这通常可以使用C++14的“通用lambda”实现。
#include <iostream>
#include <variant>
#include <string>

int main()
{
    std::variant<int, std::string> f( 0 );

    std::visit( [](auto v){std::cout << v;} , f );
}

cppreference访问页面上有更多的示例。 - M.M
谢谢!另外,我想知道index()的值 - 不过不是用在get<>中。我猜你可以通过output_visitor构造函数将f传递进去。 - KarlM
这就是答案吗?你打算如何区分访问者中的第一个 int 和第二个 int - Slava
1
@Slava 如果这种区别很重要的话(如果目标只是输出值的话则不必如此),那么你可以在任何阶段读取f.index() - M.M
@Slava:通常是通过类型别名实现的。你可以有一个variant<int, std::int32_t>;在某些实现中,后者很可能是前者的别名。你关心它是哪个索引取决于具体情况。 - Nicol Bolas
显示剩余2条评论

3

gcc 8.1的错误输出也包括了解释:

<source>:10:29: error: the value of 'fi' is not usable in a constant expression
    auto ff = std::get<fi>(f);
                        ^
<source>:9:23: note: 'fi' was not initialized with a constant expression
   const std::size_t fi = f.index();

整数模板参数必须是常量表达式。 f 不是常量表达式,因此调用其非静态成员函数不是常量表达式,因此 fi 也不是。

您可以使用以下代码获得更好的错误消息:

constexpr std::size_t fi = f.index();

只有当f也声明为constexpr时,代码get<fi>(f)才能正常工作;但是这仅在变量中的所有类型都具有平凡析构函数时才可能实现,而std::string则不具备该特性。


谢谢!我更新了一个更好的问题。如何解决问题,而不是错误信息的含义是什么。还有一个更复杂的例子。 - KarlM
@KarlM,你不能用“get<fi>”这样的代码,类型必须在编译时就确定。 - M.M
我不理解你的评论。当然,类型在编译时是已知的。 - KarlM
@KarlM 当编译 std::string bar(const foo f) 时,无法确定 f 的活动成员是 int 还是 string。因此,您不能有一个表达式引用 f 的活动成员,因为该表达式的类型在编译时是未知的。 - M.M
好的,因此是constexpr。我明白了。那我该怎么办呢? - KarlM
@KarlM 请参考Drew Dormann的答案(我不会重复解释!)。 - M.M

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接