This question already has an answer here:
Does SFINAE apply to function bodies?
(1 answer)
Closed 4 years ago.
In this code
struct A {int commmon; int rare;};
struct B {int commmon;};
struct L {
template<class T>
int f(const T& t) {return t.commmon;}
template<class T>
int f(T& t) {return t.rare;}
};
void func() {
A a; B b; L l;
l.f(a);
l.f(B{});
l.f(b);
}
the final lines gives me the error
In instantiation of ‘int L::f(T&) [with T = B]’:
error: ‘struct B’ has no member named ‘rare’
But according to my understanding of SFINAE, the second overload should be ignored because of the substitution failure in the body. Why doesn't this happen?
EDIT: If I change the return type of the second overload to decltype(T::rare), it does what I want. So where does my SF need to happen to be NAE?
The least verbose way to fix that is to use an auto return type with a trailing return type on the more constraint overload:
struct L {
template <class T>
auto f(const T& t) {return t.commmon;}
template <class T>
auto f(T& t) -> decltype(t.rare) {return t.rare;}
};
The advantage of this approach is that the constraint is specified at a point where the compiler has already seen the function argument, allowing for a shorter notation than std::enable_if clauses in the declaration of template parameters:
#include <type_traits>
struct L {
template <class T>
int f(const T& t) {return t.commmon;}
template <class T, std::enable_if_t<std::is_same_v<std::decay_t<T>, A>, int> = 0>
int f(T& t) { return t.rare;}
};
Note further that the more constrained function won't be called when passing an rvalue argument. You might want to fix that by changing the function signatures to
template<class T /*, ... */>
int f(T&& t) { /* ... */ }
SFINAE does not apply to function bodies [temp.deduct/8]:
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.
Template arguments are deduced before the function actually is instantiated. At the time the implementation is evaluated, the deduction has already occurred. So all that's relevant for deduction is the function signature, and from the existing ones, the non-const variant is fine to select.
If you want to apply get SFINAE applied depending on members existing, you have to do this within the function signature already. lubgr's answer reflects this nicely: The return type is decltype(t.rare); if T does not provide a rare member, the return type cannot be deduced and thus the overload is not considered during resolution.
Found two other answers dealing with the matter you might be interested in: C++11, compatible to pre-C++11
Related
I'm trying to perform explicit instantiation of templated functions to improve accuracy of my code coverage results. I know how to do this when the return type is known. I can indirectly achieve the desired outcome through a definition of a wrapper function that returns void. Is it possible to achieve the outcome without this additional function definition?
#include <concepts>
template <typename T>
struct Gen {
T operator()() const { return T{}; }
};
template <std::default_initializable T>
Gen<T> buildKnown() {
return Gen<T>{};
}
template <std::default_initializable T>
std::invocable auto buildConstrained() {
return Gen<T>{};
}
template <std::default_initializable T>
std::invocable auto buildInternal() /* -> decltype(??) */ {
struct Local : Gen<T> {};
return Local{};
}
// Perform explicit instantiation of each templated function.
template Gen<int> buildKnown<int>(); // works
template Gen<int> buildConstrained<int>(); // somewhat surprised this doesn't work.
template auto buildInternal<int>(); // not surprised this doesn't work
// Indirectly instantiate buildInternal through a wrapper function.
template <typename T> void buildInternalWrapper() { buildInternal<T>(); }
template void buildInternalWrapper<int>();
EDIT: Fixed type constraint (was originally std::invocable<int>) that makes the question more confusing.
Well, first of all, your deduced return type constraints are wrong. Consider for example this function template that you wrote:
template <std::default_initializable T>
std::invocable<T> auto buildConstrained() {
return Gen<T>{};
}
What will happen if this is instantiated with T = int? It will become
std::invocable<int> auto buildConstrained() {
return Gen<int>{};
}
The placeholder type specifier std::invocable<int> auto means that the type needs to be deduced, and, if we call this deduced type U, then the constraint std::invocable<U, int> must be satisfied. So this function definition is only well-formed if std::invocable<Gen<int>, int> is satisfied. But it's not; Gen<int> is invocable with 0 arguments, not with 1 int argument.
I'm guessing you meant:
template <std::default_initializable T>
std::invocable auto buildConstrained() {
return Gen<T>{};
}
Having made such an amendment, we can now declare the explicit instantiation:
template std::invocable auto buildConstrained<int>();
An explicit instantiation of a function template that uses a placeholder type specifier must also use the same placeholder type specifier, not the deduced type.
This code compiles just fine:
template <typename T1>
struct Struct {
};
struct ConvertsToStruct {
operator Struct<int>() const;
};
template <typename T>
void NonVariadicFunc(Struct<T>);
int main() {
NonVariadicFunc<int>(ConvertsToStruct{});
return 0;
}
But an attempt to make it a little more generic, by using variadic templates, fails to compile:
template <typename T1>
struct Struct {
};
struct ConvertsToStruct {
operator Struct<int>() const;
};
template <typename... T>
void VariadicFunc(Struct<T...>);
int main() {
VariadicFunc<int>(ConvertsToStruct{});
return 0;
}
What's going wrong? Why isn't my attempt to explicitly specify VariadicFunc's template type succeeding?
Godbolt link => https://godbolt.org/g/kq9d7L
There are 2 reasons to explain why this code can't compile.
The first is, the template parameter of a template function can be partially specified:
template<class U, class V> void foo(V v) {}
int main() {
foo<double>(12);
}
This code works, because you specify the first template parameter U and let the compiler determine the second parameter. For the same reason, your VariadicFunc<int>(ConvertsToStruct{}); also requires template argument deduction. Here is a similar example, it compiles:
template<class... U> void bar(U... u) {}
int main() {
bar<int>(12.0, 13.4f);
}
Now we know compiler needs to do deduction for your code, then comes the second part: compiler processes different stages in a fixed order:
cppreference
Template argument deduction takes place after the function template name lookup (which may involve argument-dependent lookup) and before template argument substitution (which may involve SFINAE) and overload resolution.
Implicit conversion takes place at overload resolution, after template argument deduction. Thus in your case, the existence of a user-defined conversion operator has no effect when compiler is doing template argument deduction. Obviously ConvertsToStruct itself cannot match anything, thus deduction failed and the code can't compile.
The problem is that with
VariadicFunc<int>(ConvertsToStruct{});
you fix only the first template parameter in the list T....
And the compiler doesn't know how to deduce the remaining.
Even weirder, I can take the address of the function, and then it works
It's because with (&VariadicFunc<int>) you ask for the pointer of the function (without asking the compiler to deduce the types from the argument) so the <int> part fix all template parameters.
When you pass the ConvertToStruct{} part
(&VariadicFunc<int>)(ConvertToStruct{});
the compiler know that T... is int and look if can obtain a Struct<int> from a ConvertToStruct and find the apposite conversion operator.
I'm experimenting with resolving the address of an overloaded function (bar) in the context of another function's parameter (foo1/foo2).
struct Baz {};
int bar() { return 0; }
float bar(int) { return 0.0f; }
void bar(Baz *) {}
void foo1(void (&)(Baz *)) {}
template <class T, class D>
auto foo2(D *d) -> void_t<decltype(d(std::declval<T*>()))> {}
int main() {
foo1(bar); // Works
foo2<Baz>(bar); // Fails
}
There's no trouble with foo1, which specifies bar's type explicitly.
However, foo2, which disable itself via SFINAE for all but one version of bar, fails to compile with the following message :
main.cpp:19:5: fatal error: no matching function for call to 'foo2'
foo2<Baz>(bar); // Fails
^~~~~~~~~
main.cpp:15:6: note: candidate template ignored: couldn't infer template argument 'D'
auto foo2(D *d) -> void_t<decltype(d(std::declval<T*>()))> {}
^
1 error generated.
It is my understanding that C++ cannot resolve the overloaded function's address and perform template argument deduction at the same time.
Is that the cause ? Is there a way to make foo2<Baz>(bar); (or something similar) compile ?
As mentioned in the comments, [14.8.2.1/6] (working draft, deducing template arguments from a function call) rules in this case (emphasis mine):
When P is a function type, function pointer type, or pointer to member function type:
If the argument is an overload set containing one or more function templates, the parameter is treated as a non-deduced context.
If the argument is an overload set (not containing function templates), trial argument deduction is attempted using each of the members of the set. If deduction succeeds for only one of the overload set members, that member is used as the argument value for the deduction. If deduction succeeds for more than one member of the overload set the parameter is treated as a non-deduced context.
SFINAE takes its part to the game once the deduction is over, so it doesn't help to work around the standard's rules.
For further details, you can see the examples at the end of the bullet linked above.
About your last question:
Is there a way to make foo2<Baz>(bar); (or something similar) compile ?
Two possible alternatives:
If you don't want to modify the definition of foo2, you can invoke it as:
foo2<Baz>(static_cast<void(*)(Baz *)>(bar));
This way you explicitly pick a function out of the overload set.
If modifying foo2 is allowed, you can rewrite it as:
template <class T, class R>
auto foo2(R(*d)(T*)) {}
It's more or less what you had before, no decltype in this case and a return type you can freely ignore.
Actually you don't need to use any SFINAE'd function to do that, deduction is enough.
In this case foo2<Baz>(bar); is correctly resolved.
Some kind of the general answer is here: Expression SFINAE to overload on type of passed function pointer
For the practical case, there's no need to use type traits or decltype() - the good old overload resolution will select the most appropriate function for you and break it into 'arguments' and 'return type'. Just enumerate all possible calling conventions
// Common functions
template <class T, typename R> void foo2(R(*)(T*)) {}
// Different calling conventions
#ifdef _W64
template <class T, typename R> void foo2(R(__vectorcall *)(T*)) {}
#else
template <class T, typename R> void foo2(R(__stdcall *)(T*)) {}
#endif
// Lambdas
template <class T, class D>
auto foo2(const D &d) -> void_t<decltype(d(std::declval<T*>()))> {}
It could be useful to wrap them in a templated structure
template<typename... T>
struct Foo2 {
// Common functions
template <typename R> static void foo2(R(*)(T*...)) {}
...
};
Zoo2<Baz>::foo2(bar);
Although, it will require more code for member functions as they have modifiers (const, volatile, &&)
I am trying to pass a generic lambda function to a boost::fusion::fold function so that I can iterate all the elements of a boost::fusion::vector. My goal is to call a non-const member function from each element in the vector. The problem is that even though the vector holds non-const values the type deduced by the generic lambda is a const reference. This results in my gcc-4.9.0 compiler (using CygWin) complaining that I am discarding the const qualifier.
#include <iostream>
#include <boost/fusion/include/vector.hpp>
#include <boost/fusion/include/fold.hpp>
#include <boost/fusion/include/for_each.hpp>
class Silly {
public:
Silly(int x)
: x_(x){}
int increment(int i) {
return x_ += i;
}
private:
int x_;
};
using my_type = boost::fusion::vector<Silly, Silly>;
int main() {
my_type my_vector(1, 2);
boost::fusion::fold(my_vector, 0, [](int i, auto& x){return x.increment(i);}); //error: passing 'const Silly' as 'this' argument of 'int Silly::increment(int)' discards qualifiers
}
Now, if instead of the lambda I pass the following functor, the program compiles cleanly
struct functor {
template <class X>
int operator()(int i, X& x) {
return x.increment(i);
}
};
Is this a boost::fusion bug or am I missing something? Thanks in advance!
There are multiple boost::fusion::fold overloads. From boost's svn repo:
template<typename Seq, typename State, typename F>
inline typename result_of::BOOST_FUSION_FOLD_NAME<
Seq const
, State const
, F
>::type
BOOST_FUSION_FOLD_NAME(Seq const& seq, State const& state, F f)
{
return result_of::BOOST_FUSION_FOLD_NAME<Seq const,State const,F>::call(
state,
seq,
f);
}
template<typename Seq, typename State, typename F>
inline typename result_of::BOOST_FUSION_FOLD_NAME<
Seq
, State const
, F
>::type
BOOST_FUSION_FOLD_NAME(Seq& seq, State& state, F f)
{
return result_of::BOOST_FUSION_FOLD_NAME<Seq,State,F>::call(
state,
seq,
f);
}
template<typename Seq, typename State, typename F>
inline typename result_of::BOOST_FUSION_FOLD_NAME<
Seq const
, State const
, F
>::type
BOOST_FUSION_FOLD_NAME(Seq const& seq, State& state, F f)
{
return result_of::BOOST_FUSION_FOLD_NAME<Seq const,State,F>::call(
state,
seq,
f);
}
The compiler is allowed to instantiate the class template result_of::BOOST_FUSION_FOLD_NAME (*) in the return type for all these variants once type deduction and substitution have succeeded, before an overload is selected. In this case, the compiler must instantiate this class template in order to determine whether or not the return type is valid. If substitution (of the template arguments) in the return type leads to an invalid type in the immediate context, the overload is discarded. This is known as SFINAE.
(*) This name typically resolves to result_of::fold.
The instantiation of one of the overloads that has a Seq const& parameter tries now to determine the return type of the lambda. However, instantiating the lambda with a Silly const& second argument fails: increment cannot be called on a const object (this is what the compiler tells you).
If determining the return type fails, this should lead to a substitution failure in the fold overload we're trying to determine the return type of. However, substitution failures due to automatic return type deduction in lambdas and C++14 functions are not in the immediate context of the original template fold: They happen within the function that uses automatic return type deduction (here: the lambda).
A substitution failure not in the immediate context of the original template is a hard error, it is not a SFINAE-type error that you could recover from. (SFINAE = SFIICINAE)
If you explicitly specify the return type of the lambda, [](int i, auto& x) -> int {return x.increment(i);}, the function/lambda does not need to be instantiated to determine the return type. It can be determined from the declaration alone. Therefore, no substitution failure based on the return type happens for any of the overloads, and usual overload resolution can select an appropriate overload. The non-const Seq& overload is chosen, and the instantiation of the lambda will be valid.
Similarly, for the explicitly written functor: If the return type can be determined without instantiating the function, no error will occur. If you use C++14's return type deduction for ordinary functions, the same problem occurs:
struct functor {
template <class X>
auto operator()(int i, X& x) {
return x.increment(i);
}
};
As a side remark: As T.C. noted in a comment, a hard error also occurs for the following function object type:
struct functor {
int operator()(int i, Silly& x) {
return x.increment(i);
}
};
The reason this fails is different, though:
Again, all fold overloads need to instantiate the result_of::fold class template with their respective types. This class template however does not produce substitution errors in the immediate context: If the function passed cannot be called with the argument types passed, a hard error will occur.
Since a function of the type int(int, Silly&) cannot be called with arguments of the type int and Silly const&, a hard error occurs.
When writing the apply operator as a template (as in the example with C++14 return type deduction), the declaration of the operator() template can be instantiated for an second argument of type Silly const& (X will be deduced to be Silly const). The function definition cannot be instantiated, though, as this will result in the same error as in the OP: Silly::increment requires a non-const Silly object.
The instantiation of the definition of the function however happens only after overload resolution if there is no return type deduction. Therefore, this will not produce substitution failures.
The following code works and the overloads are found as expected:
struct HasBuzz
{
void buzz() const {}
};
struct NoBuzz {};
template <typename T>
void foo(T const& t)
{
t.buzz();
}
void foo(NoBuzz const&){}
int main()
{
foo(HasBuzz{});
foo(NoBuzz{});
}
However, if I replace the first overload with a "universal reference" version then it no longer works. The correct overload is not found for NoBuzz.
struct HasBuzz
{
void buzz() const {}
};
struct NoBuzz {};
template <typename T>
void foo(T&& t)
{
t.buzz();
}
void foo(NoBuzz const&){}
int main()
{
foo(HasBuzz{});
foo(NoBuzz{}); // error: NoBuzz has no member function buzz
}
What can I do to make it work?
Simple solution
Add an overload that is callable with an rvalue of type NoBuzz.
void foo(NoBuzz const&){ };
void foo(NoBuzz&&) { }; // overload for rvalues
Note: Depending on your actual use case this might not be enough, because if you pass a non-const lvalue type NoBuzz to foo you'd still instantiate the template, since the two NoBuzz overloads doesn't match. At the end of this post is a more complex, but certainly cleaner, solution.
Explanation
template<class T>
void foo (T&&); // (A)
void foo (NoBuzz const&); // (B)
The problem with your snippet is that your template (A) can be instantiated in such a way that it's a better match than your overload (B).
When the compiler sees that you are trying to call a function named foo with an argument which is an rvalue of type NoBuzz, it will look for all functions named foo taking one argument where a NoBuzz would fit.
Let's say it starts of with your template (A), here it sees that T&& is deducable to any reference type (both lvalue, and rvalue), since we are passing an rvalue T = NoBuzz.
With T = NoBuzz the instantiated template would be semantically equivalent to:
void foo (NoBuzz&&); // (C), instantiated overload of template (A)
It will then continue to your overload (B). This overload accepts a const lvalue reference, which can bind to both lvalues and rvalues; but our previous template instantiation (C) can only bind to rvalues.
Since (C) is a better match than (B), binding rvalues to T&& is prefered over U const&, that overload is selected and you get the behavior you describe in your post.
Advanced solution
We can use a technique called SFINAE to conditionally make it impossible to call the template if the type passed doesn't implement .buzz ().
template <typename T>
auto foo(T&& t) -> decltype (t.buzz ())
{
return t.buzz();
}
The above solution uses a lot of new features of C++11, detailed information is available here:
wikipedia.org - C++11 - auto
wikipedia.org - decltype
Conditional overloading with trailing-return-type possible?
refp's answer explains the behavior you're seeing, and offers a possible solution. Another option is to ensure the foo function template does not make it into the candidate set for overload resolution unless T has a member function named buzz().
template <typename T>
auto foo(T&& t)
-> decltype((void)(t.buzz()), void())
{
t.buzz();
}
After making this change the foo(NoBuzz const&) overload will be selected when you pass it an instance of NoBuzz. Live demo
A detailed explanation of what's going on in the decltype expression in the trailing return type can be found here. The only thing I've done differently here is instead of using three subexpressions, with the middle one being void() to prevent a user defined operator, from being selected, I've cast the result of the first expression to void; the intent and result are identical in both cases.