问题
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