c ++使用std::enable_if有条件地将getters添加到可变变体模板

c++ Use std::enable_if to conditionally add getters to a variadic variant template

我正在尝试为我的变体具有 intfloatbool 和其他模板参数的情况添加特化。

我目前的尝试是:

#include <iostream>
#include <variant>
#include <string>
#include <type_traits>

template<typename... Types>
struct variant : std::variant<Types...> {

    using std::variant<Types...>::variant;

    template<typename T>
    const T& get() const { return std::get<T>(*this); }

#define VARGET(X) typename std::enable_if<(std::is_same<Types, X>::value ||  ... ), X>::type get_##X() const { return get<X>(); }

    VARGET(int)
    VARGET(float)
    VARGET(double)
    VARGET(bool)
    using std_string = std::string;
    VARGET(std_string)
};

int main()
{

    variant<int, float, bool> var = 3;
    std::cout << var.get_int() << std::endl;

    variant<int, float, bool> var2 = 0.1f;
    std::cout << var2.get_float() << std::endl;

    return 0;
}

但是在 gcc 9 上这给出了错误:

error: failed requirement 'std::is_same<int, double>::value || std::is_same<float, double>::value || std::is_same<bool, double>::value'; 'enable_if' cannot be used to disable this declaration

为什么这让我感到困惑:

根据我对 SFINAE 的理解,当参数之一是指定类型之一时,宏模板的计算结果为:(以 int 为例)

std::enable_if<(std::is_same<Types, int>::value || ... ), int>::type 计算结果为 int

所以表达式变为 int get_int() const { std::get<int>(*this) }

如果不是指定类型之一:

std::enable_if<(std::is_same<Types, size_t>::value || ... ), size_t>::type 计算结果为空

所以表达式变为 get_size_t() const { std::get<size_t>(*this) }

这是无效的语法,因为该函数没有 return 类型,但是由于 SFINAE,这应该仍然可以编译,因为 X 的其他替换确实会产生有效的语法。

我的代码有什么问题,有没有办法得到我想要的结果?

谢谢。

我在尝试编译您的代码时收到的错误与您的不同,请参阅 Compiler Explorer;我收到错误“no type named type in ...”,这是我预料到的,因为 SFINAE 不适用于这种情况。

如果存在会导致失败的替换,SFINAE 将在函数调用中应用。函数中没有任何内容可以被替换,代码总是错误的/总是正确的,具体取决于结构的模板参数。

创建结构时,它要么总是格式错误,要么总是格式正确。您可以通过在函数中进行人为替换来解决此问题:

template <bool f=false>
std::enable_if_t<(std::is_same<Types, int>::value ||  ...  ||f ), int> get_int() const { return get<int>(); }

那么它应该可以正常编译。

Substitution occurs in

  • 函数类型中使用的所有类型(包括return类型和所有参数的类型)
  • 模板参数声明中使用的所有类型
  • 函数类型中使用的所有表达式
  • 模板参数声明中使用的所有表达式 (C++11 起)
  • 显式说明符中使用的所有表达式 (C++20 起)

代码精简的原因其实在于, std::enable_if_t<false, int> func() {} 总是编译失败

我找到答案了。

SFINAE 函数需要是具有默认模板参数的模板。即:

#include <iostream>
#include <variant>
#include <string>
#include <type_traits>

template<typename... Types>
struct variant : std::variant<Types...> {

    using std::variant<Types...>::variant;

    template<typename T>
    const T& get() const { return std::get<T>(*this); }

#define VARGET(X) template<typename T = X> std::enable_if_t<(std::is_same_v<Types, T> ||  ... ), X> get_##X() const { return get<X>(); }

    VARGET(int)
    VARGET(float)
    VARGET(double)
    VARGET(bool)
    using std_string = std::string;
    VARGET(std_string)
};

int main()
{

    variant<int, float, bool> var = 4;
    std::cout << var.get_int() << std::endl;
    //std::cout << var.get_std_string() << std::endl; compile error

    variant<int, std::string> var2 = "hello";
    std::cout << var2.get_std_string() << std::endl;

    return 0;
}