Skip to content

C++疑难杂症(1): 小心std::variant中的bool类型值

引言

比如有这样一段代码:

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

int main(){

  std::variant<std::string, int ,bool, long long int> value{"y"};

  if(std::holds_alternative<std::string>(value)){
    std::cout << "it's  a std::string: " << std::get<std::string>(value) << std::endl;
  }else if (value.index() == 0){
    std::cout << "it's a string: " << std::get<std::string>(value) << std::endl;
  }else if (value.index() == 1){
    std::cout << "it's a int: " << std::get<int>(value) << std::endl;
 }else if (value.index() == 2){
    std::cout << "it's a bool: " << std::get<bool>(value) << std::endl;
  }
}

预期是一个string类型,但实际它认为是bool类型, 编译执行:

$ g++ -std=c++17 test.cpp
$ ./a.out
it's a bool: 1

改成:

// std::variant<std::string, int ,bool, long long int> value{"y"};
std::variant<std::string, int ,bool, long long int> value{std::string("y")};

就可以正常识别了:

$ ./a.out
it's  a std::string: y

这个问题在GCC7存在,但Clang不存在,是存在编译器差异的;需要明确的指明类型来防止这个错误,很容易忽视。

难道这是std::variant遇到bool类型有缺陷吗?实际上,这是一种普遍的隐式转换问题:

#include <iostream>
#include <string>

void foo(bool) {
    std::cout << "bool\n";
}

void foo(std::string) {
    std::cout << "string\n";
}

int main() {
    foo("hello");
    foo(std::string("hello"));
}

这个例子不管是GCC还是Clang都会认为是bool类型,找到一个解释如下:

The type of "hello" is const char [6], which decays to const char *. The conversion from const char * to bool is a built-in conversion, while the conversion from const char * to std::string is a user-defined conversion, which means the former is performed.

看起来这是历史包袱只能无可奈何了,除了明确写清楚类型之外,代替std::string的还有另外一种方法,比如:

using namespace std::string_literals;

std::variant<std::string, int ,bool, long long int> value{"y"s};

后面加个s就可以正常处理了,s表示它是不会在\0处截断的字符串,前提已经表明类型是字符串了。

总之,std::variant中有bool的时候,尤其string和bool同时出现时,警惕隐式转换。

参考