My std::variant
can be empty (std::monostate
), contain an int
, a std::string
or a bool
.
When I want to feed it with a string, given as var = "this is my string"
, it gets converted to a bool
and not to a string. If I declare the type explicitly, it works var = std::string("this is my string")
. Why is that and is there something I can to do avoid it?
#include <string>
#include <variant>
#include <iostream>
int main()
{
using var = std::variant<std::monostate, int, std::string, bool>;
var contains_nothing;
var contains_int = 5;
var contains_string = "hello";
var contains_expl_string = std::string("explicit hello");
var contains_bool = false;
auto visitor = [](auto&& arg){
using T = std::decay_t<decltype(arg)>;
if constexpr (std::is_same<T, std::monostate>())
std::cout<<"nothing\n";
else if constexpr (std::is_same<T, int>())
std::cout<<"int: "<<arg<<"\n";
else if constexpr (std::is_same<T, std::string>())
std::cout<<"string: "<<arg<<"\n";
else if constexpr (std::is_same<T, bool>())
std::cout<<"bool: "<<arg<<"\n";
else
std::cout<<"Visitor is not exhaustive\n";
};
std::visit(visitor, contains_nothing); // nothing
std::visit(visitor, contains_int); // int: 5
std::visit(visitor, contains_string); // bool: 1
std::visit(visitor, contains_expl_string); // string: explicit hello
std::visit(visitor, contains_bool); // bool: 0
}
EDIT
Since users of my code might not explicitly make string
s, I like to catch that. Otherwise it would be a source of error. I made template-helper function that checks if a char*
has been passed and if so, makes a std::string. Works well. Help for making this simpler is appreciated!
EDIT 2
By declaring std::monostate
as default parameter/type, it even works when make_var
is called without any argument.
#include <string>
#include <variant>
#include <iostream>
using var = std::variant<std::monostate, int, std::string, bool>;
template<typename T = std::monostate>
var make_var(T value = std::monostate())
{
if constexpr (std::is_same<typename std::remove_const<typename std::decay<T>::type>::type, const char*>())
return std::string(value);
return value;
}
int main()
{
auto contains_nothing = make_var();
auto contains_int = make_var(3);
auto contains_string = make_var("hello");
auto contains_expl_string = make_var(std::string("excplicit hello"));
var contains_bool = make_var(false);
auto visitor = [](auto&& arg){
using T = std::decay_t<decltype(arg)>;
if constexpr (std::is_same<T, std::monostate>())
std::cout<<"nothing\n";
else if constexpr (std::is_same<T, int>())
std::cout<<"int: "<<arg<<"\n";
else if constexpr (std::is_same<T, std::string>())
std::cout<<"string: "<<arg<<"\n";
else if constexpr (std::is_same<T, bool>())
std::cout<<"bool: "<<arg<<"\n";
else
std::cout<<"Visitor is not exhaustive\n";
};
std::visit(visitor, contains_nothing);
std::visit(visitor, contains_int);
std::visit(visitor, contains_string);
std::visit(visitor, contains_expl_string);
std::visit(visitor, contains_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.
Since you're using C++ >= 14, you can use the literal suffix s
to denote a std::string
literal:
using namespace std::string_literals;
var contains_string = "hello"s;
"hello"
isn't a std::string
, it's a const char *
and it can be implicitly converted to bool as well as used to construct a std::string
.
In other terms, this works just fine:
int main() {
bool b = "foo";
(void)b;
}
As mentioned in the comments by @aschepler:
The implicit conversion from
const char*
tobool
is preferred over the user-defined conversion fromconst char*
tostd::string
.int
is not an option at all.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With