I have code like this:
#include <type_traits>
struct S1{};
struct S2{
void rbegin(){}
void rend(){}
};
template<typename S>
struct S3{
void rbegin(){
S().rbegin();
}
void rend(){
S().rend();
}
};
template<typename, typename = void>
constexpr bool is_reverse_iterable = false;
template<typename T>
constexpr bool is_reverse_iterable<
T,
std::void_t<
decltype(std::declval<T>().rbegin()),
decltype(std::declval<T>().rend())
>
> = true;
#include <iostream>
int main(){
std::cout << is_reverse_iterable<S1> << '\n'; // print 0, correct
std::cout << is_reverse_iterable<S2> << '\n'; // print 1, correct
std::cout << is_reverse_iterable<S3<S1> > << '\n'; // print 1, wrong !!!
std::cout << is_reverse_iterable<S3<S2> > << '\n'; // print 1, correct
// S3<S1>().rbegin(); // not compiles
S3<S2>().rbegin();
}
Example is copy / paste from cppreference.com website, however, is_reverse_iterable<S3<S1> >
is set to true and this is wrong.
How can I fix this?
Godbolt link: https://gcc.godbolt.org/z/qjG46EWsq
The methods of S3
are not SFINAE-friendly. (During SFINAE, the compiler won't look inside the function body for substitution errors. It'll either ignore them if the function body isn't needed yet, or fail with a hard error on them if the body is needed, e.g. if the return type is auto
.)
In C++20 you would fix them like this:
template <typename S>
struct S3
{
void rbegin() requires requires{S().rbegin();}
// or: void rbegin() requires is_reverse_iterable<S>
{
S().rbegin();
}
void rend() requires requires{S().rend();}
{
S().rend();
}
};
In C++17 you need to do something like this:
template <typename S, typename = void>
struct S3_Base {};
template <typename S>
struct S3_Base<S, std::enable_if_t<is_reverse_iterable<S>>>
{
void rbegin()
{
S().rbegin();
}
void rend()
{
S().rend();
}
};
template <typename S>
struct S3 : S3_Base<S>
{};
Another C++17 option:
template <typename S>
struct S3
{
template <typename SS = S, std::enable_if_t<std::is_same_v<S, SS>, decltype(void(SS().rbegin()), nullptr)> = nullptr>
// or template <typename SS = S, std::enable_if_t<std::is_same_v<S, SS> && is_reverse_iterable<SS>, std::nullptr_t> = nullptr>
void rbegin()
{
S().rbegin();
}
template <typename SS = S, std::enable_if_t<std::is_same_v<S, SS>, decltype(void(SS().rend()), nullptr)> = nullptr>
void rend()
{
S().rend();
}
};