Understanding SFINAE Example - c++

I'm having trouble understanding this snippet of code which uses SFINAE.
template <typename T>
auto dist() -> typename std::enable_if<std::is_integral<T>::value,
std::uniform_int_distribution<T>>::type;
template <typename T>
auto dist() -> typename std::enable_if<std::is_floating_point<T>::value,
std::uniform_real_distribution<T>>::type;
...
decltype(dist<float>()) unifDistFloat;
decltype(dist<int>()) unifDistInt;
dist() is the name of two different function prototypes, so there is no body containing a return statement. Meaning it never actually return a value of the type uniform_real_distribution<T>, or uniform_int_distribution<T>.
So shouldn't decltype fail trying to call an incomplete function? Or does decltype just not call the function at all and instead just evaluate the return type?

decltype is an unevaluated context. It just operates on the type level, so knowing that the body of dist, wherever it might be, returns some type X is enough.

decltype specifier inspects the declared type of an entity or queries the return type of an expression, thats what it does.
here an alternative usage. for showing that decltype cares only for type of any passing expression
template<typename U >
static typename std::enable_if<std::is_same<U, int>::value, std::uniform_int_distribution <U>>::type
dist(); // NOTE: no function body
template<typename U >
static typename std::enable_if<std::is_same<U, double>::value, std::uniform_real_distribution <U>>::type
dist()
{
//return; // NOTE: no return
}
decltype(dist<T>()) mUniformDistribution;
probably the Question why compiler does complain about dist() function as incomplete? i have no idea why is that.
demo example
template<typename T >
class Random
{
public:
Random(const T& min, const T& max)
: mUniformDistribution(min, max)
{}
T operator()()
{
return mUniformDistribution(mEngine);
}
private:
std::default_random_engine mEngine{ std::random_device()() };
template<typename U >
static typename std::enable_if<std::is_same<U, int>::value, std::uniform_int_distribution <U>>::type
dist(); // NOTE: no function body
template<typename U >
static typename std::enable_if<std::is_same<U, double>::value, std::uniform_real_distribution <U>>::type
dist()
{
//return; // NOTE: no return
}
decltype(dist<T>()) mUniformDistribution;
};
int main()
{
Random<int> getRandom(0, 9);
for (int i = 0; i<9; ++i)
std::cout << getRandom() << '\n';
}

Related

How to write constexpr function which operate on forwarded tuple of reference?

I have written a constexpr function which calculate the sum of the sizes of the elements of a tuple.
When called directly, the function call compiles with both tuple of values, and tuple of references.
When called through a templated function, it still compiles with tuple of values, but fail with tuple of references.
I can work around my problem using tuple of pointers instead of tuple of reference, but the API of the thing I write (a templated set of functions to ease writing SPI and I²C driver for microcontrollers) will be less clean.
Thanks for any help.
Ps: I am using gcc8.2 using c++17 standard.
Alexandre
#include <tuple>
template <typename> struct is_tuple_t: std::false_type {};
template <typename ...T> struct is_tuple_t<std::tuple<T...>> : std::true_type {};
template<typename Type>
constexpr bool is_tuple(const Type&) {
if constexpr (is_tuple_t<Type>::value)
return true;
else
return false;
}
template<class F, class...Ts, std::size_t...Is>
constexpr void for_each_in_tuple(const std::tuple<Ts...> & tupl, F func,
std::index_sequence<Is...>){
using expander = int[];
(void)expander { 0, ((void)func(std::get<Is>(tupl)), 0)... };
}
template<class F, class...Ts>
constexpr void for_each_in_tuple(const std::tuple<Ts...> & tupl, F func){
for_each_in_tuple(tupl, func, std::make_index_sequence<sizeof...(Ts)>());
}
template <typename T>
constexpr size_t size_of_tuple(const T &tup) {
static_assert(is_tuple(tup) == true, "error size_of_tuple argument must be a tuple");
size_t s=0;
for_each_in_tuple(tup, [&s](auto &&x) {
s += sizeof(x);
});
return s;
}
template<typename Tb>
constexpr size_t foo(const Tb&& tup)
{
constexpr size_t st = size_of_tuple(tup);
return st;
}
int main()
{
uint16_t var;
constexpr size_t s1 = size_of_tuple(std::make_tuple(1)) ; // OK
constexpr size_t s2 = size_of_tuple(std::forward_as_tuple(var)) ; // OK
constexpr size_t f1 = foo(std::make_tuple(1)) ; // OK
constexpr size_t f2 = foo(std::forward_as_tuple(var)) ; // FAIL
}
template<typename Tb>
constexpr size_t foo(const Tb&& tup)
{
constexpr size_t st = size_of_tuple(tup);
return st;
}
In this function, tup is not a constant expression, so it can't be used in the initializer of a constexpr variable.
[expr.const]¶2
An expression e is a core constant expression unless the evaluation of e [...] would evaluate one of the following expressions:
[...]
an id-expression that refers to a variable or data member of reference type unless the reference has a preceding initialization and either
it is initialized with a constant expression or
its lifetime began within the evaluation of e
Either one of these should work instead:
template<typename Tb>
constexpr size_t foo(const Tb&& tup)
{
size_t st = size_of_tuple(tup);
return st;
}
template<typename Tb>
constexpr size_t foo(const Tb&& tup)
{
return size_of_tuple(tup);
}
Note that Clang still rejects your code, for other violations of the same rule.
Ultimately, your size_of_tuple and is_tuple are both flawed, as they can't be used in constant expressions if their argument is a reference. If you want to use this function-like syntax, you need something like type_c from Boost.Hana:
template <typename T>
class type {};
template <typename T>
constexpr type<T> type_c{};
template <typename T>
constexpr bool is_tuple(type<T>) {
return is_tuple_t<T>::value;
}
template <typename T>
constexpr size_t size_of_tuple(type<T> tup) {
static_assert(is_tuple(tup), "size_of_tuple argument must be a tuple");
//...
}
template<typename Tb>
constexpr size_t foo(const Tb&& tup)
{
size_t st = size_of_tuple(type_c<std::remove_cvref_t<decltype(tup)>>);
return st;
}
uint16_t var = 42;
constexpr size_t f2 = foo(std::forward_as_tuple(var));
The problems of your original approach are properly explained in Oktalist's answer.
Note that size_of_tuple can be implemented in one line using C++17's fold expressions:
template<class... Ts>
constexpr std::size_t size_of_tuple(const std::tuple<Ts...>&) {
return (0 + ... + sizeof(Ts));
}
However, that function is hard to use because of the argument of type const std::tuple<Ts...>&. Thus it may be desirable to introduce an empty tag type that can be "passed" to these kind of metafunctions. This idea is explained in Boost.Hana's chapter on type computations.
The following is a complete example.
static_assert(__cplusplus >= 201703L, "C++17 required");
#include <cstddef>
#include <cstdint>
#include <tuple>
// the empty tag type
template<class T>
struct Type {};
////////////////////////////////////////////////////////////////////////////////
template<class... Ts>
constexpr std::size_t size_of_tuple(Type< std::tuple<Ts...> >) {
return (0 + ... + sizeof(Ts));
}
static_assert(0 == size_of_tuple(Type< std::tuple<> >{}));
static_assert(12 == size_of_tuple(Type< std::tuple<int32_t, int64_t> >{}));
static_assert(12 == size_of_tuple(Type< std::tuple<int32_t&, int64_t&> >{}));
static_assert(2 == size_of_tuple(Type< std::tuple<char&, char> >{}));
static_assert(6 == size_of_tuple(Type< std::tuple<int32_t&, char&, char> >{}));
// fails to compile (for good reasons):
//static_assert(8 == size_of_tuple(Type< std::tuple<int32_t, uint64_t> >{}));
Furthermore, you can consider to use std::integral_constant as return type. That may become handy when you want to pass the size into another function and use it as a constexpr value. Note that std::integral_constants are implicitly convertible to their ::type. The implicit conversion is one reason why Boost.Hana introduces its own "integral-constant type". Some interesting examples can be found in Boost.Hana's chapter on Compile-time numbers. Anyway, here is the simple example with the implicitly convertible std::integral_constant:
#include <cstddef>
#include <tuple>
#include <type_traits>
template<class... Ts>
constexpr auto better_size_of_tuple(Type< std::tuple<Ts...> >) {
constexpr std::size_t ret = (0 + ... + sizeof(Ts));
return std::integral_constant<std::size_t, ret>{};
}
so here is a rewrite of the function which compile, at least with gcc 8.2 :
template
<std::size_t position, class T>
struct SizeOfTupImpl{
static constexpr size_t
size() {
if constexpr (position != 0) {
return sizeof(std::tuple_element_t<position, T>) +
SizeOfTupImpl<position - 1, T>::size();
} else {
return sizeof(std::tuple_element_t<0, T>);
}
}
};
template<class T>
constexpr size_t
size_of_tuple(const T& tup) {
static_assert(is_tuple(tup) == true, "error size_of_tuple argument must be a tuple");
constexpr std::size_t dimension = std::tuple_size<T>::value;
return SizeOfTupImpl<dimension - 1, T>::size();
}
Since i am an absolute template metaprogramming beginner, feel free to point me toward a more elegant solution !
Alexandre

recursion in variadic template function of different argument types

consider the following piece of code
template <int INDEX>
void foo() { } // termination version
template <int INDEX, typename Arg, typename... Args>
void foo(Arg head, Args... args) {
if (INDEX == 0) {
cout << head << endl;
}
else {
foo <INDEX-1 > (args...);
}
}
int main() {
foo<1> (1, 3.1415);
return 0;
}
the code compiles and outputs 3.1415 as expected.
however, the following simple code compiles fine but always outputs 1. do you have any fix for this?
template <int INDEX>
void foo() { } // termination version
template <int INDEX, typename Arg, typename... Args>
Arg foo(Arg head, Args... args) {
if (INDEX == 0) {
return head;
}
else {
foo <INDEX-1 > (args...);
}
}
int main() {
cout<<foo<1> (1, 3.1415,"Test!");
return 0;
}
in other words, how can I recursively call a variadic templated function with different argument types?
1 Problems with your approach
1.1 Missing return in foo<1>
Make sure you understand how a return from a nested call works. Your foo<1> calls foo<0> which returns its (foo<0>'s) first argument back to foo<1>. But your foo<1> does not care about foo<0>'s return because it called foo<0> like this:
else {
foo<i-1>(args...);// `i-1` becomes `0`
}
The compiler knows you have a problem here: Which value should foo<1> return after it got the return from foo<0> (which has been ignored)? It has to return a value of the same type as its first argument, but it never returns before reaching its closing }.
As pointed out in the comments, you should turn on compiler warnings to detect problems like these. In this case, -Wall (GCC documentation on warning options) is sufficient for GCC and clang to warn you (online demo), but there are more warnings available. If your filename reads main.cpp and the closing } is found line 23, column 1, the compiler warning could read
main.cpp: In function ‘Arg foo(Arg, Args ...) [with int INDEX = 1; Arg = int; Args = {double, const char*}]’:
main.cpp:23:1: warning: control reaches end of non-void function [-Wreturn-type]
}
^
1.2 Return type must be known at compile time
You might attempt to fix your code by passing the return value from foo<0> up the stack:
else {
return foo<i-1>(args...);// NOTE: type of return value depends on `foo<i-1>`
}
However, that fails because foo<1> has been declared to return a value of the same type as its first argument:
template<int i, class Arg, class... Args>
Arg foo(Arg, Args... args) {// <--------- NOTE: must return a value of type `Arg`
2 Fix for your own recursive implementation
2.1 C++17 and above
With C++17 you can use auto as return type together with constexpr if to implement the recursion as follows:
template<size_t i, class T0, class... Ts>
auto foo(T0 v0, Ts... vs) {
static_assert(i < 1u + sizeof...(Ts));
if constexpr(0u == i) return v0;// <------ NOTE: must be `if constexpr` (C++17)
else return foo<i-1u>(vs...);
}
2.2 C++14 and above
With C++14 you can also use auto as return type, but constexpr if is not available. The workaround is a well-known idiom and uses specialization of a class templates that "implements" the recursion logic:
template<int i>
struct foo_impl {
static_assert(i > 0, "the case `i == 0` requires a specialization");
template<class T0, class... Ts>
static auto get(T0, Ts... vs) {
return foo_impl<i-1>::get(vs...);
}
};
template<>
struct foo_impl<0> {
template<class T0, class... Ts>
static auto get(T0 v0, Ts...) {
return v0;
}
};
template<int i, class... Ts>
auto foo(Ts... vs) {
static_assert(i >= 0 && i < sizeof...(Ts), "index range: [0, size)");
return foo_impl<i>::get(vs...);// forward to "implementation"
}
2.3 C++11 and above
With C++11 you would need to specify trailing return types which is a bit tedious. See max66's answer for details.
3 Final recommendations
Enable and analyze compiler warnings (-Wall is an absolute minimum).
Once you are familiar with these techniques, do not implement this yourself. Instead, learn and use standard solutions like std::tuple.
Use compile-time recursion with caution. It may significantly increase your compilation time.
I don't think it's possible (in C++11 and C++14, at least) develop a foo() of this type because you don't know the correct return type.
If you don't want use std::tuple, I suggest to develop a type traits to extract the n-th type and manage foo() via SFINAE.
The following is a possible solution
#include <iostream>
#include <type_traits>
template <std::size_t, typename...>
struct indexType
{ using type = int; }; // the type of the foo() without argument
template <std::size_t I, typename I0, typename ... Is>
struct indexType<I, I0, Is...>
{ using type = typename indexType<I-1U, Is...>::type; };
template <typename I0, typename ... Is>
struct indexType<0U, I0, Is...>
{ using type = I0; };
template <std::size_t I, typename ... Args>
using indexType_t = typename indexType<I, Args...>::type;
template <std::size_t>
int foo ()
{ return 0; } // termination version: a return type is needed
template <std::size_t I, typename Arg, typename... Args>
auto foo (Arg const & head, Args const & ...)
-> typename std::enable_if<I == 0U, Arg>::type
{ return head; }
template <std::size_t I, typename Arg, typename... Args>
auto foo (Arg const &, Args const & ... args)
-> typename std::enable_if<I != 0U, indexType_t<I-1U, Args...>>::type
{ return foo<I-1U>(args...); }
int main ()
{
std::cout << foo<1U> (1, 3.1415, std::string("Test!")) << std::endl;
std::cout << foo<2U> (1, 3.1415, std::string("Test!")) << std::endl;
std::cout << foo<3U> (1, 3.1415, std::string("Test!")) << std::endl;
}

Variadic functions addition in c++ with constrained types

My goal is to do a simple addition of any number of parameters as long as they are of the following types -
integer (e.g. 123)
string represented as integer (e.g. "123")
If they are of some other type, I ignore them.
Approach
I'm using a variadic function approach.
Within the function, I check for type. If the type is int, then I add recursively. Else, I ignore the argument, and recur on further arguments.
Here's what I think the code looks like --
// BASE
template <typename T>
int func(T t)
{
string type= typeid(t).name();
if (type==typeid(int).name())
return stoi(t);
else if (type==typeid(const char*).name())
return atoi(t);
else
return 0;
}
// RECUR
template<typename T, typename... Args>
int func(T t, Args... args) // recursive variadic function
{
string type = typeid(t).name();
if (type==typeid(int).name()){
int sum = t;
return sum+func(args...);
}
else
return func(args...);
}
// MAIN
int main()
{
// All testing here in MAIN.
// [2]
int funcres = func('a',1, 2.5000,"123");
cout << funcres << endl;
return 0;
}
This gives me the expected answer: 124.
However,
I made following observations which tell me that my code is not fail-safe.
Why is stoi required in this line of the base function?
if (type==typeid(int).name())
return stoi(t);
If I do not do this and call just return t, I get an error when I call my function.
Cannot initialize return object of type 'int' with an lvalue of type 'const char *'
This doesn't make sense to me when I've already specified that return the integer if the type is integer.
Even after I do return stoi(t) (which I don't understand why is required in the first place), and return atoi(t) if the type is const char* then inserting "japan" or "123" at the beginning or in the middle in the template arg list [for.e.g func(1,2,2.5000,"123",12);] causes the code to complain at this point.
int sum = t;
The error is same as above.
Cannot initialize return object of type 'int' with an lvalue of type 'const char *'
Is using variadic function the best way or are there alternatives?
If it's the best way (it seems so to me since I need any number of parameters and any type of parameters to be considered for adding), what am I doing wrong?
C++ templates are resolved statically, meaning that substituting the parameters has to work for all substitutions, even those which are unreachable at runtime. However by using overloads with a helper function instead of RTTI we can handle the conversion a lot more cleanly:
template<class T>
int forceInt(T arg) { return 0; }
int forceInt(int arg) { return arg; }
int forceInt(std::string arg) { return std::stoi(arg); }
int forceInt(const char * arg) { return std::stoi(arg); }
With this helper function you can do a simple recursive sum:
int func() { return 0; }
template<typename T, typename... Args>
int func(T t, Args... args) // recursive variadic function
{
return forceInt(t) + func(args...);
}
This can also be expanded to handle any integer type. By using SFINAE on the general overload to restrict it to non-integer types, this causes the int overload to become prefered for integral types. However char is integral so we also need to add a char overload of 0 if you don't want that to be implicitly converted to an int:
template<class T, class U = typename std::enable_if<!std::is_integral<T>::value>::type>
int forceInt(T arg) { return 0; }
int forceInt(char arg) {return 0;}
Overloading is one possibility. You could also do it with some template magic. This has the advantage, that the list over which you are summing is pruned at compile time from all incompatible types (except the last element, which is substituted by 0 if it is no match).
#include <cassert>
#include <string>
#include <type_traits>
template < typename T >
struct is_const_char : std::false_type {};
template < >
struct is_const_char < const char * > : std::true_type {};
template < typename T >
struct is_int : std::false_type {};
template < >
struct is_int < int > : std::true_type {};
// Break condition
template < typename T >
typename std::enable_if<is_int<T>::value, int>::type
sum(T t)
{
return t;
}
template < typename T >
typename std::enable_if<is_const_char<T>::value, int>::type
sum(T t)
{
return std::stoi(t);
}
template < typename T >
typename std::enable_if<!is_int<T>::value && !is_const_char<T>::value, int>::type
sum(T)
{
return 0;
}
// Forward declarations
template < typename T, typename ... Args >
typename std::enable_if<is_const_char<T>::value, int>::type
sum(T, Args ...);
template < typename T, typename ... Args >
typename std::enable_if<is_int<T>::value, int>::type
sum(T, Args ...);
// Recursions
template < typename T, typename ... Args >
typename std::enable_if<!is_int<T>::value && !is_const_char<T>::value, int>::type
sum(T, Args ... args)
{
return sum(args...);
}
template < typename T, typename ... Args >
typename std::enable_if<is_int<T>::value, int>::type
sum(T t, Args ... args)
{
return t + sum(args...);
}
template < typename T, typename ... Args >
typename std::enable_if<is_const_char<T>::value, int>::type
sum(T t, Args ... args)
{
return std::stoi(t) + sum(args...);
}
// Test it
int main()
{
assert( sum('a', 1, 2, 3, "123", 4, 5) == 138 );
assert( sum('a',1, 2.5000,"123") == 124 );
}

counting nested types in pairs and tuples

I need to count types in nested pairs and tuples and have come up with this snippet. If I use gcc's declval(), however, the following error occurrs:
/usr/include/c++/5.3.0/type_traits:2204:7: error: static assertion failed: declval() must not be used!
static_assert(__declval_protector<_Tp>::__stop,
struct swallow
{
template <typename ...T>
explicit swallow(T&& ...) noexcept
{
}
};
template <typename C>
constexpr inline decltype(auto) declval() noexcept
{
return ::std::move(*static_cast<C*>(nullptr));
}
template <typename T>
constexpr inline auto count_types(T const&) noexcept
{
return 1;
}
template <typename ...A>
constexpr inline auto count_types(::std::pair<A...> const&) noexcept
{
int r{};
swallow{(r += count_types(declval<A>()))...};
return r;
}
template <typename ...A>
constexpr inline auto count_types(::std::tuple<A...> const&) noexcept
{
int r{};
swallow{(r += count_types(declval<A>()))...};
return r;
}
int main()
{
::std::cout << count_types(declval<::std::tuple<int, int, ::std::pair<int, ::std::pair<int, int>>>>());
return 0;
}
My question is why and why does my declval implementation work correctly, while gcc's doesn't? I'll try to solve the problem using meta template programming next.
EDIT: Here's a fix, that compiles with both gcc and clang:
struct swallow
{
template <typename ...T>
constexpr explicit swallow(T&& ...) noexcept
{
}
};
template <typename C>
constexpr inline decltype(auto) declval() noexcept
{
return static_cast<typename std::remove_reference<C>::type&&>(*static_cast<C*>(nullptr));
}
template <typename T>
constexpr inline auto count_types(T const&) noexcept
{
return 1;
}
template <typename ...A>
constexpr inline auto count_types(::std::pair<A...> const&) noexcept
{
int r{};
swallow{(r += count_types(declval<A>()))...};
return r;
}
template <typename ...A>
constexpr inline auto count_types(::std::tuple<A...> const&) noexcept
{
int r{};
swallow{(r += count_types(declval<A>()))...};
return r;
}
int main()
{
::std::cout << ::std::integral_constant<int, count_types(declval<::std::tuple<int, int, ::std::pair<int, ::std::pair<int, int>>>>())>{};
return 0;
}
std::declval is intended exclusively for compile-time logic. It is defined as, from [declval]:
template <class T>
add_rvalue_reference_t<T> declval() noexcept; // as unevaluated operand
There is no body to that function. It's just there to give you a type. You cannot do runtime operations with it. That's not what it's for.
Your implementation of declval involves explicitly dereferencing a null pointer, at runtime. That's just a misunderstanding of what declval is for and how to use it. Additionally, your implementation of declval doesn't support lvalue references (e.g. I can std::declval<Foo&>() gives me an lvalue reference, your_declval<Foo&>() is ill-formed since you're taking a pointer to a reference).
The way to write something like this would be based on types. You can still write a function that takes a std::tuple<A...> and returns the number of nested types - it's just that that cannot be a runtime operation, it should encode that result into a type. That is, we start with:
struct adl_tag {};
template <class T>
inline auto count_types_impl(adl_tag, T const&)
-> std::integral_constant<int, 1>;
template <class T>
using count_types = decltype(count_types_impl(adl_tag{}, std::declval<T>()));
And then just add other overloads for count_types_impl() for the other types we wish to support. The adl_tag is there so we can find all the overloads we need. Using fold-expressions to make this answer shorter, we can then add:
template <class... A>
inline auto count_types_impl(adl_tag , std::tuple<A...> const&)
-> std::integral_constant<int, (count_types<A>::value + ...)>;
template <class A, class B>
inline auto count_types_impl(adl_tag , std::pair<A,B> const&)
-> std::integral_constant<int, (count_types<A>::value + count_types<B>::value)>;
So that:
std::cout << count_types<std::tuple<int, std::pair<int,int>>>::value; // prints 3
Note that at no point is anything evaluated. None of these functions even have a body!
std::declval is not supposed to return an actual value, and it seems libstdc++ guards against it further to give you a more helpful message than a linker error would provide.
Note that because no definition exists for declval, it can only be used in unevaluated contexts; it is an error to evaluate an expression that contains this function. Formally, the program is ill-formed if this function is odr-used.
-- cppreference
Also, this: return ::std::move(*static_cast<C*>(nullptr)); seems hardly safe, dereferencing a null pointer is undefined behavior...

C++ why does SFINAE fail with only a class template parameter?

I'm using SFINAE in the style of this answer in order to call a generic vector object by using an appropriate member function. For example, the following code calls operator[](int) const first, and if that doesn't exist then operator()(int) const:
template<int I> struct rank : rank<I-1> { static_assert(I > 0, ""); };
template<> struct rank<0> {};
template<typename VectorType>
struct VectorWrapper
{
auto get(int i) const
{
return get(v, i, rank<5>());
}
template<typename V, typename = std::enable_if_t<has_bracket_operator<const V>::value> >
auto get(V const& v, int i, rank<2>) const
{
return v[i];
}
template<typename V, typename = std::enable_if_t<has_parenthesis_operator<const V>::value> >
auto get(V const& v, int i, rank<1>) const
{
return v(i);
}
VectorType v;
};
With the has_bracket_operator and has_parenthesis_operator traits set up as suggested in this thread, the whole compiles and seems to work.
However, passing the member vector to the overloaded class templates seems unnecessary from the first, so I tried to set up the same without passing it. For this, I replaced the template parameter V with the VectorType parameter used to set up the class template:
template<typename = std::enable_if_t<has_bracket_operator<VectorType>::value> >
auto get(int i, rank<2>) const
{
return v[i];
}
template<typename = std::enable_if_t<has_parenthesis_operator<VectorType>::value> >
auto get(int i, rank<1>) const
{
return v(i);
}
Now, however, the compilation fails (in gcc 5.1.0) with the following error message:
/usr/local/include/c++/5.1.0/type_traits: In substitution of 'template<bool _Cond, class _Tp> using enable_if_t = typename std::enable_if::type [with bool _Cond = has_parenthesis_operator<std::vector<int> >::value; _Tp = void]':
main.cpp:46:10: required from 'struct VectorWrapper<std::vector<int> >'
main.cpp:59:38: required from here
/usr/local/include/c++/5.1.0/type_traits:2388:61: error: no type named 'type' in 'struct std::enable_if<false, void>'
using enable_if_t = typename enable_if<_Cond, _Tp>::type;
DEMO
Questions:
What is the reason for this compilation error?
Is there an appropriate workaround other than that of my first code block? (That is, one that retains the usual coding style -- where one does not have to pass members).
SFINAE comes to us from [temp.deduct]/8, emphasis mine:
If a substitution results in an invalid type or expression, type deduction fails. An invalid type or expression is
one that would be ill-formed, with a diagnostic required, if written using the substituted arguments. [ Note:
If no diagnostic is required, the program is still ill-formed. Access checking is done as part of the substitution
process. —end note ] Only invalid types and expressions in the immediate context of the function type and
its template parameter types can result in a deduction failure.
The immediate context is what's in the template declaration. In your initial example:
template<typename V, typename = std::enable_if_t<has_bracket_operator<const V>::value> >
auto get(V const& v, int i, rank<2>) const
V is in the immediate context, so a substitution failure on the enable_if is just a deduction failure.
However, in your second example:
template<typename = std::enable_if_t<has_bracket_operator<VectorType>::value> >
auto get(int i, rank<2>) const
VectorType is not in the immediate context of get, so a failure here would not be a deduction failure, it would be a hard error.
Unless VectorType happens to have all of these operators.
The solution to any template problem is to just add more template. In this case, force VectorType to be in the immediate context by introducing another type:
template<typename T=VectorType, typename = std::enable_if_t<has_bracket_operator<T>::value> >
auto get(int i, rank<2>) const
And call get<>().
In your failing example, the template parameter VectorType has already been determined by the time get is being resolved. To make SFINAE work, you need to make the template parameters you are using for SFINAE resolve at that method call. The following is a modification of your first example to work like you want to:
template<int I> struct rank : rank<I-1> { static_assert(I > 0, ""); };
template<> struct rank<0> {};
template<typename VectorType>
struct VectorWrapper
{
auto get(int i) const
{
return get(v, i, rank<5>());
}
template<typename V=VectorType, typename = std::enable_if_t<has_bracket_operator<const V>::value> >
auto get(int i, rank<2>) const
{
return v[i];
}
template<typename V=VectorType, typename = std::enable_if_t<has_parenthesis_operator<const V>::value> >
auto get(int i, rank<1>) const
{
return v(i);
}
VectorType v;
};
This way, V is resolved when get is called, and it will correctly use SFINAE.
Or you can just use tag-dispatching:
auto get(int i) const
{
return get(i, has_bracket_operator<VectorType>(), has_parenthesis_operator<VectorType>());
}
auto get(int i, std::true_type /*brackets*/, std::false_type /*parenthesis*/) const
{
return v[i];
}
auto get(int i, std::false_type /*brackets*/, std::true_type /*parenthesis*/) const
{
return v(i);
}
demo