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

可紊 提交于 2021-02-08 09:12:22

问题


I am trying to add specializations for the case where my variant has any of int, float, bool, and others as template arguments.

My attempt so far is:

#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;
}

But on gcc 9 this gives the error:

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

Why this confuses me:

From my understanding of SFINAE, when one of the arguments is one of the specified types, the macro template evaluates to: (For the example of int)

std::enable_if<(std::is_same<Types, int>::value || ... ), int>::type evaluates to int

so the expression becomes int get_int() const { std::get<int>(*this) }

And if it is not one of the specified types:

std::enable_if<(std::is_same<Types, size_t>::value || ... ), size_t>::type evaluates to nothing

so the expression becomes get_size_t() const { std::get<size_t>(*this) }

This is invalid syntax because the function does not have a return type, but because of SFINAE this should still compile because of other substitutions of X do produce valid syntax.

What is wrong with my code, and is there a way to get the result I desire?

Thanks.


回答1:


The error I am receiving when trying to compile your code differs from yours, see on Compiler Explorer; I receive the error "no type named type in ..." which I would have expected, as SFINAE does not apply in this case.

SFINAE would apply for instance in a function call if there is a substitution which whould cause a failure. Nothing were to be substituted in the function, the code is always wrong / always correct depending on the struct's template arguments.

When creating the struct, it is either always malformed, or always well-formed. You might be able to work around this by imposing an artificial substitution in the function:

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

Then it should compile fine.

Substitution occurs in

  • all types used in the function type (which includes return type and the types of all parameters)
  • all types used in the template parameter declarations
  • all expressions used in the function type
  • all expressions used in a template parameter declaration (since C++11)
  • all expressions used in the explicit specifier (since C++20)

The reason why the code is condensed best in the fact, that std::enable_if_t<false, int> func() {} will always fail to compile.




回答2:


I figured out the answer.

The SFINAE function needs to be a template with a default template argument. i.e:

#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;
}


来源:https://stackoverflow.com/questions/61137647/c-use-stdenable-if-to-conditionally-add-getters-to-a-variadic-variant-templa

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!