9

Consider the following:

struct foo {
};

struct bar {
};

int main()
{
    foo f;
    bar b;
    std::variant<foo*, bool> v;
    v = &b; // compiles in Visual Studio 19 v16.7.3
}

As discussed in comments, I believe the above is legal C++17. There is a proposal, P0608R3, that was accepted into the standard addressing this kind of surprising behavior, but it was accepted in 2018 (at the San Diego meeting) and thus applies to C++20 not C++17. Further P0608R3 is not currently implemented in Visual Studio, even when compiling to the C++20 preview.

What is the best / least verbose way to make creation of this variant from a pointer that points to a non-foo a compile time error? I believe the following works but is a lot of boilerplate if the variant contains several items.

struct foo {
};

struct bar {
};

using variant_type = std::variant<foo*, bool>;
struct var_wrapper : public variant_type
{
    var_wrapper(foo* v = nullptr) : variant_type(v)
    {}

    var_wrapper(bool v) : variant_type(v)
    {}

    template<typename T>
    var_wrapper(T*) = delete;
};

int main()
{
    foo f;
    bar b;

    var_wrapper vw;
    vw = &f; // fine
    vw = true; // fine
    vw = &b; // compile time error
}

Am I missing some simpler way?

jwezorek
  • 8,592
  • 1
  • 29
  • 46
  • Looks like an [error](https://godbolt.org/z/Wc8f3j) to me. – cigien Oct 07 '20 at 18:08
  • It compiles in Visual Studio 2019. – jwezorek Oct 07 '20 at 18:09
  • Ok, then could you add that tag please? And add that info to the question? – cigien Oct 07 '20 at 18:10
  • 1
    I have a feeling you could make this wrapper generic and static assert that the type of the pointer is exactly the type of (or a derived class of) one of the types in the variant. – Borgleader Oct 07 '20 at 18:12
  • 1
    This looks like a bug to me. cppreference says that if the type list contains a possibly cv-qualified `bool`, then that type should be activated by `operator=` only if its argument is exactly a possibly cv-qualified `bool`. That is, no implicit conversion should take place. – Igor G Oct 07 '20 at 18:20
  • So this is a bug in Visual Studio? – jwezorek Oct 07 '20 at 18:23
  • So it seems. You'd better ask for confirmation from a language lawyer, though. – Igor G Oct 07 '20 at 18:29
  • 7
    @jwezorek According to https://learn.microsoft.com/en-us/cpp/overview/visual-cpp-language-conformance?view=vs-2019 this ([P0608R3](https://wg21.link/P0608R3)) just hasn't been implemented yet in the MSVC standard library. – Artyer Oct 07 '20 at 18:30
  • P0608R3 was accepted for C++20 not 17 though, right? – jwezorek Oct 07 '20 at 23:05
  • 1
    I am afraid that you are right. – Barrnet Chou Oct 08 '20 at 01:58

2 Answers2

2

Another solution is to introduce another bool wrapper that doesn't construct from anything except from bool:

#include <variant>

struct foo {};
struct bar {};

struct StrongBool {
    bool value = false;

    StrongBool() noexcept = default;
    StrongBool(bool b) noexcept : value(b) {}

    template<class T>
    StrongBool(T) = delete;
};

int main() {
    foo f;
    bar b;
    std::variant<foo*, StrongBool> v;
    v = true;
    v = &f;
    v = &b; // fails to compile
} 

Regardless, limiting acceptable initializers requires introducing type wrappers with user-defined constructors.

Maxim Egorushkin
  • 131,725
  • 17
  • 180
  • 271
0

I think the cleanest way to handle implicit conversion doing surprising things with respect to variants is to use a "strong variant" type if the behavior of std::variant is a problem; i.e., implement a variant type that enforces construction only using types that are exactly the types in the variant.

It is easy to test if a type is a member of a parameter pack in C++17 using std::disjunction, leading to an implementation as below:

template<typename... Ts>
class strong_variant : public std::variant<Ts...> 
{
public:
    template <typename T, typename U = 
        typename std::enable_if<std::disjunction_v<std::is_same<T, Ts>...>>::type>
    strong_variant(T v) : std::variant<Ts...>(v)
    {}

    strong_variant() : std::variant<Ts...>()
    {}
};

struct foo {};
struct bar {};

int main()
{
    foo f;
    bar b;
    const foo c_f;

    strong_variant<foo*, std::string, bool> sv;

    sv = &f; // okay.
    sv = true; // okay.
    sv = "foo"s; // okay.

    sv = "foo"; //no, must a string.
    sv = &b;  // no, must be a foo.
    sv = &c_f; // no, must be non-const.
}
jwezorek
  • 8,592
  • 1
  • 29
  • 46
  • I am glad you have got your solution and thanks for your sharing, I would appreciate it if you mark them as answer and this will be beneficial to other community. – Barrnet Chou Oct 14 '20 at 01:37