Why does my variant convert a std::string to a bool?
Asked Answered
M

2

17

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 strings, 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);    
}
Minta answered 20/5, 2017 at 13:8 Comment(3)
Another question to ask is, why should std::string be preferred to bool? Both require array decay and type conversion.Davilman
See my lovely answer to #44022489 especially my workaround using a template ;-). But Angew's use of a user defined literal (below) is cuter.Bolshevik
short answer: it doesn't, because that's not an std::string you gave it.Notable
W
34

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;
Wabble answered 20/5, 2017 at 13:13 Comment(4)
Why didn't it pick up the int type instead of bool?Hyperon
@Hyperon Because there is no implicit pointer to int conversion in C++.Wabble
It makes sense indeed. Thank you. :-)Hyperon
Is there a possible fix to prevent conversion from const char* to string without the need to be explicit?Minta
H
4

"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* to bool is preferred over the user-defined conversion from const char* to std::string. int is not an option at all.

Hyperon answered 20/5, 2017 at 13:12 Comment(2)
No UB here. The implicit conversion from const char* to bool is preferred over the user-defined conversion from const char* to std::string. int is not an option at all.Glissando
@Glissando Thank you, fixed.Hyperon

© 2022 - 2024 — McMap. All rights reserved.