What kinds of templates may be used with is_detected? - c++

I'm interested in understanding what sorts of templates may be used as the first argument of std::experimental::is_detected and similar detection utilities.
Pasted below is an implementation of is_detected and an attempt at using it to detect whether a type T has a member function named .foo(). The general pattern it uses is to first define a type trait which returns the type of a member function T::foo, if it exists:
template<class T>
struct member_foo_result
{
using type = decltype(std::declval<T>().foo());
};
Then, it defines a shorthand alias:
template<class T>
using member_foo_result_t = typename member_foo_result<T>::type;
The problem is that the shorthand alias seems to be incompatible with is_detected.
Here's the full program, with compiler output:
#include <utility>
#include <type_traits>
#include <iostream>
// implementation of is_detected
template<class...>
using void_t = void;
struct nonesuch
{
nonesuch() = delete;
~nonesuch() = delete;
nonesuch(const nonesuch&) = delete;
void operator=(const nonesuch&) = delete;
};
template<class Default, class AlwaysVoid,
template<class...> class Op, class... Args>
struct detector
{
using value_t = std::false_type;
using type = Default;
};
template<class Default, template<class...> class Op, class... Args>
struct detector<Default, void_t<Op<Args...>>, Op, Args...>
{
using value_t = std::true_type;
using type = Op<Args...>;
};
template<template<class...> class Op, class... Args>
using is_detected = typename detector<nonesuch, void, Op, Args...>::value_t;
// returns the result type of T::foo()
template<class T>
struct member_foo_result
{
using type = decltype(std::declval<T>().foo());
};
// shorthand alias for member_foo_result<T>::type
template<class T>
using member_foo_result_t = typename member_foo_result<T>::type;
// detects whether or not the member function T::foo() exists
template<class T>
struct has_foo_member : is_detected<member_foo_result_t, T> {};
struct doesnt_have_foo_member {};
int main()
{
std::cout << "result: " << has_foo_member<doesnt_have_foo_member>::value << std::endl;
}
I expect this program to print "result: 0" at runtime. However, it does not compile correctly:
$ clang -std=c++11 test_is_detected.cpp
test_is_detected.cpp:41:43: error: no member named 'foo' in 'doesnt_have_foo_member'
using type = decltype(std::declval<T>().foo());
~~~~~~~~~~~~~~~~~ ^
test_is_detected.cpp:45:1: note: in instantiation of template class 'member_foo_result<doesnt_have_foo_member>' requested here
using member_foo_result_t = typename member_foo_result<T>::type;
^
test_is_detected.cpp:27:33: note: in instantiation of template type alias 'member_foo_result_t' requested here
struct detector<Default, void_t<Op<Args...>>, Op, Args...>
^
test_is_detected.cpp:33:1: note: during template argument deduction for class template partial specialization 'detector<type-parameter-0-0, void, Op, type-parameter-0-2...>' [with Default = nonesuch, Op =
member_foo_result_t, Args = <doesnt_have_foo_member>]
using is_detected = typename detector<nonesuch, void, Op, Args...>::value_t;
^
test_is_detected.cpp:50:25: note: in instantiation of template type alias 'is_detected' requested here
struct has_foo_member : is_detected<member_foo_result_t, T> {};
^
test_is_detected.cpp:58:30: note: in instantiation of template class 'has_foo_member<doesnt_have_foo_member>' requested here
std::cout << "result: " << has_foo_member<doesnt_have_foo_member>::value << std::endl;
^
1 error generated.
Am I misusing is_detected?

is_detected is meant to be used with alias templates that cause an error in the immediate context (i.e., SFINAE-friendly) when instantiated with "wrong" arguments.
That means that the substitution failure to be detected needs to occur in the alias template itself, rather than some class template it instantiates:
template<class T>
using member_foo_result_t = decltype(std::declval<T>().foo());

Related

SFINAE type trait with pointer-to-member-function fails

I'm practicing SFINAE and would like to implement a type trait in order to check if a given class T contains a method print(). I have the following two variants:
// (1)
template <typename T, typename = int>
struct has_print_method : std::false_type {};
template <typename T>
struct has_print_method<T, decltype(&T::print, 0)> : std::true_type {};
template <typename T>
const bool has_print_method_v = has_print_method<T>::value;
// (2)
template <typename T>
struct has_print_method{
template <typename U, typename = void> struct helper : std::false_type{};
template <typename U> struct helper<U, decltype(&U::print)> : std::true_type{};
static const bool value = helper<T, void (T::*)() const>::value;
};
template <typename T>
const bool has_print_method_v = has_print_method<T>::value;
(1) only checks for the existence of a member method print() and ignores the member method's signature. Whereas (2) checks the method's signature, i.e. it requires a member method void print() const.
I test both variants via:
#include <iostream>
#include <type_traits>
// Simple Class A
struct A{
int a;
public:
void print() const{}
};
// (1) or (2) here
template<typename T, std::enable_if_t<has_print_method_v<T>, bool> = true>
void print(T t) {
t.print();
}
void print(double x){
std::cout << x << '\n';
}
int main() {
A a;
print(a);
print(1.0); // (*)
return 0;
}
Using the type trait (1) and compiling with clang 12.0 and std=c++17 flag works as expected. However, using (2) instead, I obtain
<source>:28:50: error: member pointer refers into non-class type 'double'
static const bool value = helper<T, void (T::*)() const>::value;
^
<source>:32:33: note: in instantiation of template class 'has_print_method<double>' requested here
const bool has_print_method_v = has_print_method<T>::value;
^
<source>:34:39: note: in instantiation of variable template specialization 'has_print_method_v<double>' requested here
template<typename T, std::enable_if_t<has_print_method_v<T>, bool> = true>
^
<source>:35:6: note: while substituting prior template arguments into non-type template parameter [with T = double]
void print(T t) {
^~~~~~~~~~~~
<source>:47:5: note: while substituting deduced template arguments into function template 'print' [with T = double, $1 = (no value)]
print(1.0);
^
1 error generated.
What am I missing here? You can find the example here on godbolt. Edit: Um, I have just noticed that both versions compile without an error with gcc11.1. Strange.
First up, judging by your error messages and my own tests, I think you mean that type trait (1) works and (2) doesn't. On that basis, here is a version of trait (1) that tests for a matching function signature:
template <typename T, typename = int>
struct has_print_method : std::false_type {};
template <typename T>
struct has_print_method<T, decltype(&T::print, 0)> : std::is_same <decltype(&T::print), void (T::*)() const> {};
template <typename T>
const bool has_print_method_v = has_print_method<T>::value;
Live demo

SFINAE on Error in Dependent Type causes unexpected hard error

I have code that I can simplify down to something like this:
#include <type_traits>
template <typename T>
struct dependent
{
using type = typename T::type;
};
template <typename T>
typename dependent<T>::type
foo(const T& x);
bool foo(bool x) { return x; }
int main()
{
foo(true);
}
This fails to compile with g++ 9.3 with --std=c++17 with the error:
test.cpp: In instantiation of 'struct dependent<bool>':
test.cpp:11:1: required by substitution of 'template<class T> typename dependent<T>::type foo(const T&) [with T = bool]'
test.cpp:17:13: required from here
test.cpp:6:11: error: 'bool' is not a class, struct, or union type
6 | using type = typename T::type;
| ^~~~
This is not what I would expect. I would expect that attempting to substitute bool for T in template <typename T> typename dependent<T>::type foo(const T& x) would be a failure, which is not an error. It seems SFINAE is not working for me, but I do not know why.
From the examples in the unofficial reference on SFINAE:
Substitution proceeds in lexical order and stops when a failure is encountered.
template <typename A>
struct B { using type = typename A::type; };
template <
class T,
class = typename T::type, // SFINAE failure if T has no member type
class U = typename B<T>::type // hard error if T has no member type
// (guaranteed to not occur as of C++14)
> void foo (int);
I am hitting the case on class U = typename B<T>::type, but the "guaranteed to not occur as of C++14" bit seems to indicate that this should not be happening as of C++14. What gives?
Issue is that dependent<T> has type, but that one might be ill formed causing hard failure.
You might make dependent SFINAE friendly:
template <typename T, typename Enabler = void>
struct dependent
{
};
template <typename T>
struct dependent<T, std::void_t<typename T::type>>
{
using type = typename T::type;
};
Demo

SFINAE failing to work with intermediary type traits

Consider the following test code:
// Preprocessor
#include <iostream>
#include <type_traits>
// Structure with no type alias
template <class T>
struct invalid {
};
// Structure with a type alias
template <class T>
struct valid {
using type = T;
};
// Traits getting the type of the first type
template <class T, class... Args>
struct traits {
using type = typename T::type;
};
// One argument function
template <class T, class = typename traits<T>::type>
void function(T) {
std::cout << "function(T)" << std::endl;
}
// Two arguments function
template <class T, class U, class = typename traits<T, U>::type>
void function(T, U) {
std::cout << "function(T, U)" << std::endl;
}
// When function can be called on all arguments
template <
class... Args,
class = decltype(function(std::declval<Args>()...))
>
void sfinae(Args&&... args) {
function(std::forward<Args>(args)...);
std::cout << "sfinae(Args&&...)" << std::endl;
}
// When function can be called on all arguments except the first one
template <
class T,
class... Args,
class = decltype(function(std::declval<Args>()...))
>
void sfinae(const invalid<T>&, Args&&... args) {
function(std::forward<Args>(args)...);
std::cout << "sfinae(const invalid<T>&, Args&&...)" << std::endl;
}
// Main function
int main(int argc, char* argv[]) {
valid<int> v;
invalid<int> i;
sfinae(v);
sfinae(i, v);
return 0;
}
The code involves:
a structure invalid that has no ::type
a structure valid that has a ::type
a structure traits that defines ::type as T::type
an overloaded function which should work only if the type of the first argument is such that traits<T>::type is defined
an overloaded sfinae function that should be able to call function even if the first argument is invalid
However, the SFINAE mechanism does not seem to work in this instance, and I am not sure to understand why. The error is the following:
sfinae_problem_make.cpp:19:30: error: no type named 'type' in 'invalid<int>'
using type = typename T::type;
~~~~~~~~~~~~^~~~
sfinae_problem_make.cpp:29:46: note: in instantiation of template class 'traits<invalid<int>, valid<int> >' requested here
template <class T, class U, class = typename traits<T, U>::type>
^
sfinae_problem_make.cpp:30:6: note: in instantiation of default argument for 'function<invalid<int>, valid<int> >' required here
void function(T, U) {
^~~~~~~~~~~~~~~~
sfinae_problem_make.cpp:37:22: note: while substituting deduced template arguments into function template 'function' [with T = invalid<int>, U = valid<int>, $2 = (no value)]
class = decltype(function(std::declval<Args>()...))
^
sfinae_problem_make.cpp:39:6: note: in instantiation of default argument for 'sfinae<invalid<int> &, valid<int> &>' required here
void sfinae(Args&&... args) {
^~~~~~~~~~~~~~~~~~~~~~~~
sfinae_problem_make.cpp:60:5: note: while substituting deduced template arguments into function template 'sfinae' [with Args = <invalid<int> &, valid<int> &>, $1 = (no value)]
sfinae(i, v);
The very surprising thing is that if traits is removed from the problem:
// Preprocessor
#include <iostream>
#include <type_traits>
// Structure with no type alias
template <class T>
struct invalid {
};
// Structure with a type alias
template <class T>
struct valid {
using type = T;
};
// Traits getting the type of the first type
template <class T, class... Args>
struct traits {
using type = typename T::type;
};
// One argument function
template <class T, class = typename T::type>
void function(T) {
std::cout << "function(T)" << std::endl;
}
// Two arguments function
template <class T, class U, class = typename T::type>
void function(T, U) {
std::cout << "function(T, U)" << std::endl;
}
// When function can be called on all arguments
template <
class... Args,
class = decltype(function(std::declval<Args>()...))
>
void sfinae(Args&&... args) {
function(std::forward<Args>(args)...);
std::cout << "sfinae(Args&&...)" << std::endl;
}
// When function can be called on all arguments except the first one
template <
class T,
class... Args,
class = decltype(function(std::declval<Args>()...))
>
void sfinae(const invalid<T>&, Args&&... args) {
function(std::forward<Args>(args)...);
std::cout << "sfinae(const invalid<T>&, Args&&...)" << std::endl;
}
// Main function
int main(int argc, char* argv[]) {
valid<int> v;
invalid<int> i;
sfinae(v);
sfinae(i, v);
return 0;
}
then it works as expected and outputs:
function(T)
sfinae(Args&&...)
function(T)
sfinae(const invalid<T>&, Args&&...)
Question: Why the first version does not work, and is there a way to make it work with the intermediary type traits?
SFINAE requires substitution failures to be "in the immediate context" of the instantiation. Otherwise a hard error will occur.
Without the intermediate traits type, the instantiation of function<invalid<int>, valid<int>, invalid<int>::type> causes an error in the immediate context because invalid<int> doesn't have a member named type, so SFINAE kicks in.
With the intermediate traits type, the error occurs during the instantiation of the definition traits<invalid<int>> since this requires the nonexistent invalid<int>::type. This is not in the immediate context, so a hard error occurs.
To fix this, you must ensure that traits always has a valid definition. This can be done like so:
template <class T, class = void>
struct traits {};
template <class T>
struct traits<T, std::void_t<typename T::type>> {
using type = typename T::type;
};
Fundamentally, this boils down to what "immediate context" means in [temp.deduct]/8, the sfinae rule, which isn't super clearly defined (see cwg 1844):
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, its template parameter types, and its explicit-specifier can result in a deduction failure. [ Note: The substitution into types and expressions can result in effects such as the instantiation of class template specializations and/or function template specializations, the generation of implicitly-defined functions, etc. Such effects are not in the “immediate context” and can result in the program being ill-formed. — end note ]
In this case, arguably the immediate context is just to see that traits<T,U>::type is a thing that exists. Which it does. But it's only when we go through and instantiate that type as the default argument that we have to look at what T::type is. But that's a little delayed from what we actually need.
What you need is to either force the instantiation of traits itself to fail or force traits to not have a member alias named type if T does not. The cop-out short version would just be:
template <class T, class... Args>
struct traits;
template <class T>
struct traits<valid<T>> {
using type = T;
};
But you'll want something slightly more robust than that.
Unfortunately, you cannot add a trailing defaulted template argument like:
template <typename T, typename... Args, typename = typename T::type>
struct traits {
using type = typename T::type;
};
due to [temp.param]/15, but with Concepts, you could do:
template <typename T>
concept Typed = requires {
typename T::type;
};
template <Typed T, typename... Args>
struct traits {
using type = typename T::type;
};
If you read the description of SFINAE, there is this sentence:
Only the failures in the types and expressions in the immediate context of the function type or its template parameter types or its explicit specifier (since C++20) are SFINAE errors.
That traits<T, U>::type is accessed within the immediate context of function and not that of sfinae. This is why it results in a compiler error.

Writing custom c++14-style type aliases

I wanted to create a _t alias for std::is_base_of, similar to how std::enable_if_t is an alias for std::enable_if. So I added the following to my standard header:
namespace std {
template<typename T, typename U>
using is_base_of_t = typename is_base_of<T,U>::type;
}
I'm using this new alias in the following context:
template <typename SpaceT,
typename TagT,
typename = std::enable_if_t<std::is_base_of_t<Space,SpaceT>>>
class SpatialTree : public SpaceT {
};
And I'm getting the following gcc error:
SpatialTree.h:48:101: error: type/value mismatch at argument 1 in template parameter list for ‘template<bool _Cond, class _Tp> using enable_if_t = typename std::enable_if::type’
template <typename SpaceT, typename TagT, typename = std::enable_if_t<std::is_base_of_t<Space,SpaceT>>>
What am I doing wrong?
std::enable_if is defined thusly:
template< bool B, class T = void >
struct enable_if;
The first argument is a value, not a type. You need to pass in std::is_base_of<Space,SpaceT>::value as the first argument. With C++14, the correct alias would be a variable template:
template <class Base, class Derived>
constexpr bool is_base_of_v = std::is_base_of<Base, Derived>::value;
that you'd use the same way:
template <typename SpaceT, typename TagT,
typename = std::enable_if_t<is_base_of_v<Space,SpaceT>>>
^^^
class SpatialTree : public SpaceT {
};
The latter currently exists in <experimental/type_traits>.
enable_if[_t] wants a bool as its first argument. You have to write std::enable_if_t<std::is_base_of_t<Space,SpaceT>::value> or std::enable_if_t<std::is_base_of_t<Space,SpaceT>{}>, otherwise you pass a type where a bool is required.
However, you might want to define a counterpart of std::experimental::is_base_of_v instead:
template <class Base, class Derived>
constexpr bool is_base_of_v = std::is_base_of<Base, Derived>{};
and use as std::enable_if_t<is_base_of_v<Space, SpaceT>>.
That said, I wouldn't define either of these templates in namespace std, since that invokes UB as per [namespace.std]/1.
Available already as of Clang 3.6 and g++ 5.1
#include <experimental/type_traits>
using namespace std::experimental;
struct B {}; struct D : B {};
int main()
{
static_assert(is_base_of_v<B, D>, "");
}

Disable Function when parameter type is void

I have a template class looking like this:
template <typename T> constexpr bool is_value_passable_v = is_trivially_copyable_v<T> && sizeof(T) <= sizeof(void*) && !is_polymorphic_v<T>;
template <typename B, typename T> using param_base_t = conditional_t<is_value_passable_v<B>, T, const T&>;
template <typename T> struct param_d
{
using type = param_base_t<T, T>;
};
template <> struct param_d<void>
{
using type = void;
};
template <typename T> using param_t = typename param_d<T>::type;
template <class TIn> class CClass
{
public:
static constexpr bool use_input_v = !is_same_v<typename TIn::input_t, void>;
using input_t = conditional_t<use_input_v, param_t<typename TIn::input_t>, void>;
enable_if_t<use_input_v> Input(input_t i);
};
The goal of this code is, to provde different Input functions for different template paramters.
A template parameter with input_t = int should result in void Input(int i)
A template parameter with input_t = std::vector should result in void Input(const std::vector& i)
A template parameter with input_t = void should remove the Input function
Compiling this with clang gives
/usr/bin/../include/c++/v1/type_traits:225:78: error: no type named 'type' in 'std::__1::enable_if<false, void>'; 'enable_if' cannot be used to disable this declaration
template <bool _Bp, class _Tp = void> using enable_if_t = typename enable_if<_Bp, _Tp>::type;
^~~
Edit 1:
After adding the line
template <typename T> static constexpr bool use_input2_v = use_input_v;
and replacing the function declaration with
template <typename T = void> enable_if_t<use_input2_v<T>> Input(input_t i)
clang complains there's no matching member function for call to 'Input':
note: candidate template ignored: substitution failure [with T = void]: non-type template argument is not a constant expression
template <typename T = void> enable_if_t<use_input2_v<T>> Input(input_t i);
~~~~~~~~~~~~ ^
Edit 2:
Forgot to mention, that this error comes with all three variants of template parameters.
Edit 3:
A sample use case for CClass could be
class CInput0
{
using input_t = int;
};
class CInput1
{
using input_t = std::vector<int>;
};
class CInput2
{
using input_t = void;
};
CClass<CInput0> in0;
CClass<CInput1> in1;
CClass<CInput2> in2;
std::vector<int> i = {1, 2, 3};
in0.Input(3);
in1.Input(i);
//in2.Input() disabled
For SFINAE`to work, it needs to be working off a dependent type, otherwise there is no substitution failure. Here is an example:
template <typename Self = CClass<TIn>>
typename std::enable_if<Self::use_input_v>::type
Input(typename Self::input_t) { }
When a member function is a template, the compiler conditionally creates it based on whether the template parameters work. In your original example, since the whole class is a template, but the method is not, the compiler just sees it as an error with your member function as soon as the class is instantiated. Using a default template parameter is just the trick we need. What we want to test is now considered dependent.
If you want to enable a function based on the properties of a template parameter, without partial specialization you can use the following pattern:
#include <iostream>
#include <type_traits>
#include <vector>
std::ostream& operator<<(std::ostream& out, std::vector<int>& value);
template <class TIn> class CClass
{
public:
template <class T =TIn, class PARAM= std::enable_if_t<!(std::is_same<void,typename T::input_t>::value) ,typename T::input_t > >
void Input(PARAM i){
std::cout << "Called Input with parameter: "<< i << std::endl;
}
};
struct CInput0{ using input_t = int;};
struct CInput1{ using input_t = std::vector<int>;};
struct CInput2{ using input_t = void;};
CClass<CInput0> in0;
CClass<CInput1> in1;
CClass<CInput2> in2;
std::vector<int> i = {1, 2, 3};
std::ostream& operator<<(std::ostream& out, std::vector<int>& value) {
for (auto& e:value) {
out << e;
}
out << std::endl;
return out;
}
int main() {
in0.Input(3);
in1.Input(i);
//in2.Input() disabled
}
This is a simplified version of your example, which you should be able to adapt.