I'm working on code like following one
#include <functional>
template <typename Type>
void foo(const std::function<void(const Type&)> & handler) {}
void goo (const int&){}
int main() {
foo([](const int&){});
foo(goo);
}
unfortunate it refuses to compile on (clang 6.0.0 and gcc 8.1.1) due to following error
candidate template ignored: could not match 'function<void (const type-parameter-0-0 &)>' against '(lambda at test3.cpp:13:9)'
candidate template ignored: could not match 'function<void (const type-parameter-0-0 &)>' against '(lambda at test3.cpp:13:9)'
Is it possible to somehow force it to deduce Type correctly?
You tagged C++17, so you can use deduction guides for std::function's.
You can try something as follows
template <typename F,
typename Type = typename decltype(std::function{std::declval<F>()})::argument_type>
void foo (F f)
{
}
I know that argument_type is deprecated in C++17, but you can substitute it with a simple custom template.
By example
template <typename>
struct firstArg;
template <typename R, typename A0, typename ... As>
struct firstArg<std::function<R(A0, As...)>>
{ using type = A0; };
and foo() can be written as
template <typename F,
typename FUNC = decltype(std::function{std::declval<F>()}),
typename Type = typename firstArg<FUNC>::type>
void foo (F f)
{
}
This way the callable f isn't a std::function but it's original type (and this can be better or worse, depending from your exact requirements); if you need it in a std::function, you can obtain it inside the foo() function using again deduction guides or the FUNC type
template <typename F,
typename FUNC = decltype(std::function{std::declval<F>()}),
typename Type = typename firstArg<FUNC>::type>
void foo (F f)
{
FUNC fnc{f};
}
Related
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
Consider following code
template<typename T>
T modify(const T& item, std::function<T(const T&)> fn)
{
return fn(item);
}
When trying to use it as modify(5, [](const int& i){return 10*i;}); it fails to compile with
could not deduce template argument for 'std::function<T(const T &)> from lambda
I know that compiler can not deduce T from lambda, because lambda is not std::function, but isn't T already deduced from 5?
I can get over it using
template<typename T, typename F>
T modify(const T& item, const F& functor)
{
return functor(item);
}
for which previous example compiles, but it is in my opinion less intuitive. Is there a way to let the function argument to remain std::function and have it's template argument deduced automatically from item?
What you basically want to do is prevent deduction from happening. If template deduction occurs, it will fail (because a lambda is not a std::function<> - it doesn't matter that T was deduced from the first argument, deduction must succeed in every argument that is a deduced context). The way to prevent deduction is to stick the entire argument in a non-deduced context, the easiest way of doing that is to throw the type into a nested-name-specifier. We create such a type wrapper:
template <class T> struct non_deduce { using type = T; };
template <class T> using non_deduce_t = typename non_deduce<T>::type;
And then wrap the type in it:
template<typename T>
void foo(const T& item, std::function<void(T)> f);
template<typename T>
void bar(const T& item, non_deduce_t<std::function<void(T)>> f);
foo(4, [](int ){} ); // error
bar(4, [](int ){} ); // ok, we deduce T from item as int,
// which makes f of type std::function<void(int)>
Note, however, that:
template <typename T, typename F>
void quux(const T&, F );
is not really any less readable, and strictly more performant.
You can do it by using the identity trick as below:
template <typename T>
struct identity {
typedef T type;
};
template<typename T>
T modify(const T& item, typename identity<std::function<T(const T&)>>::type fn) {
return fn(item);
}
Live Demo
When I compile this code:
#include <type_traits>
template <typename T>
void do_stuff(std::enable_if_t<std::is_integral<T>::value, T> &t) {}
template <typename T>
void do_stuff(std::enable_if_t<std::is_class<T>::value, T> &t) {}
int main() {
int i = 1;
do_stuff(i);
return 0;
}
GCC says:
37325975.cpp: In function ‘int main()’:
37325975.cpp:11:15: error: no matching function for call to ‘do_stuff(int&)’
do_stuff(i);
^
37325975.cpp:4:6: note: candidate: template<class T> void do_stuff(std::enable_if_t<std::is_integral<_Tp>::value, T>&)
void do_stuff(std::enable_if_t<std::is_integral<T>::value, T> &t) {}
^
37325975.cpp:4:6: note: template argument deduction/substitution failed:
37325975.cpp:11:15: note: couldn't deduce template parameter ‘T’
do_stuff(i);
^
37325975.cpp:7:6: note: candidate: template<class T> void do_stuff(std::enable_if_t<std::is_class<T>::value, T>&)
void do_stuff(std::enable_if_t<std::is_class<T>::value, T> &t) {}
^
37325975.cpp:7:6: note: template argument deduction/substitution failed:
37325975.cpp:11:15: note: couldn't deduce template parameter ‘T’
do_stuff(i);
^
I've also tried on msvc 2013.
Why do I get these errors?
Live Demo
As the compiler says, that parameter type is non-deducible, so you would need to supply the template argument manually, like this:
do_stuff<int>(i);
A better option is to put the std::enable_if in the return type or template parameter list:
//Return type
template <typename T>
std::enable_if_t<std::is_integral<T>::value>
do_stuff(T &t) {}
template <typename T>
std::enable_if_t<std::is_class<T>::value>
do_stuff(T &t) {}
//Parameter list
template <typename T, std::enable_if_t<std::is_integral<T>::value>* = nullptr>
void do_stuff(T &t) {}
template <typename T, std::enable_if_t<std::is_class<T>::value>* = nullptr >
void do_stuff(T &t) {}
This way the template parameter can still be deduced:
do_stuff(i);
Why do I get these errors?
Because template argument deduction fails for nested-name-specifier, which is non-deduced contexts.
The nested-name-specifier (everything to the left of the scope resolution operator ::) of a type that was specified using a qualified-id.
// the identity template, often used to exclude specific arguments from deduction
template<typename T> struct identity { typedef T type; };
template<typename T> void bad(std::vector<T> x, T value = 1);
template<typename T> void good(std::vector<T> x, typename identity<T>::type value = 1);
std::vector<std::complex<double>> x;
bad(x, 1.2); // P1 = std::vector<T>, A1 = std::vector<std::complex<double>>
// P1/A1: deduced T = std::complex<double>
// P2 = T, A2 = double
// P2/A2: deduced T = double
// error: deduction fails, T is ambiguous
good(x, 1.2); // P1 = std::vector<T>, A1 = std::vector<std::complex<double>>
// P1/A1: deduced T = std::complex<double>
// P2 = identity<T>::type, A2 = double
// P2/A2: uses T deduced by P1/A1 because T is to the left of :: in P2
// OK: T = std::complex<double>
When the compiler tries to resolve do_stuff(int&), it sees the two candidates that the compiler tells you about. But it's not able to "work backwards" to find a T that satisfies std::enable_if_t<std::is_integral<T>::value, T> == int, nor to find a T that satisfies std::enable_if_t<std::is_class<T>::value, T> == int.
As mentioned in TartanLlama's answer, the way to avoid this is to make the argument deducible (e.g. do_stuff(T&)) and make the return type or a subsequent template argument dependent on T.
I tried to use std::enable_if on a function parameter to trigger SFINAE. Compilation fails with this error:
type_nonsense.cpp:20:5: error: no matching function for call to 'c'
c(SOME::VALUE);
^
type_nonsense.cpp:13:6: note: candidate template ignored: couldn't infer
template argument 'T'
void c(typename std::enable_if<std::is_enum<T>::value, T>::type t) {}
^
1 error generated.
Moving the std::enable_if to either the return type or to a dummy template parameter works fine. Why?
#include <type_traits>
// Works
template <typename T, typename dummy = typename std::enable_if<std::is_enum<T>::value, T>::type>
void a(T t) {}
// Works
template <typename T>
typename std::enable_if<std::is_enum<T>::value, void>::type b(T t) {}
// Fails to compile
template <typename T>
void c(typename std::enable_if<std::is_enum<T>::value, T>::type t) {}
enum class SOME { VALUE };
int main() {
a(SOME::VALUE);
b(SOME::VALUE);
c(SOME::VALUE);
}
A dependent type in a nested name specifier is a non-deduced context for template argument deduction, and as such cannot be used to determine the type of T. Placing the std::enable_if in either the return type or as a default template parameter works because the type of T isn't being deduced in those contexts.
If you need to place it as a parameter, you can do so like this:
template <typename T>
void c(T t, typename std::enable_if<std::is_enum<T>::value>::type* = nullptr) {}
This works because T is being deduced by the first argument, not the second.
For the one which fails to compile, T is not deductible.
I just asked this question: std::numeric_limits as a Condition
I understand the usage where std::enable_if will define the return type of a method conditionally causing the method to fail to compile.
template<typename T>
typename std::enable_if<std::numeric_limits<T>::is_integer, void>::type foo(const T &bar) { isInt(bar); }
What I don't understand is the second argument and the seemingly meaningless assignment to std::enable_if when it's declared as part of the template statement, as in Rapptz answer.
template<typename T, typename std::enable_if<std::is_integral<T>::value, int>::type = 0>
void foo(const T& bar) { isInt(); }
As is mentioned in comment by 40two, understanding of Substitution Failure Is Not An Error is a prerequisite for understanding std::enable_if.
std::enable_if is a specialized template defined as:
template<bool Cond, class T = void> struct enable_if {};
template<class T> struct enable_if<true, T> { typedef T type; };
The key here is in the fact that typedef T type is only defined when bool Cond is true.
Now armed with that understanding of std::enable_if it's clear that void foo(const T &bar) { isInt(bar); } is defined by:
template<typename T>
typename std::enable_if<std::numeric_limits<T>::is_integer, void>::type foo(const T &bar) { isInt(bar); }
As mentioned in firda's answer, the = 0 is a defaulting of the second template parameter. The reason for the defaulting in template<typename T, typename std::enable_if<std::is_integral<T>::value, int>::type = 0> is so that both options can be called with foo< int >( 1 );. If the std::enable_if template parameter was not defaulted, calling foo would require two template parameters, not just the int.
General note, this answer is made clearer by explicitly typing out typename std::enable_if<std::numeric_limits<T>::is_integer, void>::type but void is the default second parameter to std::enable_if, and if you have c++14 enable_if_t is a defined type and should be used. So the return type should condense to: std::enable_if_t<std::numeric_limits<T>::is_integer>
A special note for users of visual-studio prior to visual-studio-2013: Default template parameters aren't supported, so you'll only be able to use the enable_if on the function return: std::numeric_limits as a Condition
template<typename T, std::enable_if<std::is_integral<T>::value, int>::type = 0>
void foo(const T& bar) { isInt(); }
this fails to compile if T is not integral (because enable_if<...>::type won't be defined). It is protection of the function foo.The assignment = 0 is there for default template parameter to hide it.
Another possibility: (yes the typename is missing in original question)
#include <type_traits>
template<typename T, typename std::enable_if<std::is_integral<T>::value, int>::type = 0>
void foo(const T& bar) {}
template<typename T>
typename std::enable_if<std::is_integral<T>::value>::type
bar(const T& foo) {}
int main() {
foo(1); bar(1);
foo("bad"); bar("bad");
}
error: no matching function for call to ‘foo(const char [4])’
foo("bad"); bar("bad");
^
note: candidate is:
note: template::value, int>::type > void foo(const T&)
void foo(const T& bar) {}
^
note: template argument deduction/substitution failed:
error: no type named ‘type’ in ‘struct std::enable_if’
template::value, int>::type = 0>
^
note: invalid template non-type parameter
error: no matching function for call to ‘bar(const char [4])’
foo("bad"); bar("bad");
^