如何在构造std::variant时禁用指针类型到bool的隐式转换?

9

请考虑以下内容:

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
}

如评论所述,我认为上述代码符合C++17标准。有一个提案P0608R3已被采纳,用于解决这种令人惊讶的行为,但它是在2018年(在圣地亚哥会议上)被接受的,因此适用于C++20而不是C++17。 此外,在将代码编译为C++20预览版时,即使在Visual Studio中,P0608R3也没有得到实现。
如何以最佳/最简短的方式使指向非foo的指针创建此变体成为编译时错误?如果变量包含多个项,则以下代码可以工作,但需要大量样板文件。
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
}

我是否漏过了一些更简单的方法?


1
我有一种感觉,你可以使这个包装器通用化,并静态断言指针的类型恰好是变体中某个类型的类型(或派生类)。 - Borgleader
1
这对我来说看起来像是一个bug。cppreference上说,如果类型列表包含一个可能带有cv限定符的bool,那么只有当它的参数恰好是一个可能带有cv限定符的bool时,该类型才应该被operator=激活。也就是说,不应该进行任何隐式转换。 - Igor G
那么这是Visual Studio的一个错误吗? - jwezorek
7
根据https://learn.microsoft.com/en-us/cpp/overview/visual-cpp-language-conformance?view=vs-2019,这个([P0608R3](https://wg21.link/P0608R3))在MSVC标准库中尚未实现。 - Artyer
1
我恐怕你是对的。 - Barrnet Chou
显示剩余5条评论
2个回答

2
另一种解决方案是引入另一个布尔包装器,除了从布尔值构造外不接受任何其他参数:
#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
} 

无论如何,限制可接受的初始化程序需要引入具有用户定义构造函数的类型包装器。


0

我认为处理隐式转换与变量相关的令人惊讶的行为最干净的方法是,如果 std::variant 的行为有问题,则使用“强变体”类型;即,实现一种变体类型,该类型仅强制使用与变体中完全相同的类型进行构造。

在 C++17 中,使用 std::disjunction 可以轻松测试类型是否是参数包的成员,从而导致以下实现:

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.
}

我很高兴你找到了解决方案,感谢你的分享。如果您能将它们标记为答案,这将有益于其他社区成员。 - Barrnet Chou

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