I suppose the basic premise of this question is that I'm trying to use enable_if
along with Argument Dependent Lookup (ADL), but I'm not sure if it's possible. I do see on this page that
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.
So I imagine this won't work, but in the spirit of learning I'd like to put the question out there.
Here's an example of what I'm trying to get to happen:
#include <iostream>
namespace lib1 {
template <typename T>
void archive(T & t)
{
serialize(t);
}
}
namespace lib2 {
struct VectorInt {
int x;
int y;
};
struct VectorDouble {
double x;
double y;
};
template<typename T>
void serialize(std::enable_if<std::is_same<T, VectorInt>::value, T>::type & vect) {
std::cout << vect.x << std::endl;
}
// maybe do something different with VectorDouble. Overloading would work,
// but I'm curious if it can be made to work with enable_if
}
int main() {
lib2::VectorInt myvect;
myvect.x = 2;
lib1::archive(myvect);
}
The example is loosely based on something I'm trying to do with the cereal library. In my case I have several different types of vectors and matrices, and while I can use overloading to get functions to resolve properly, I was curious to use the enable_if
feature to see if I could shorten the code.
Anyway, trying to compile that gives a message "error: variable or field 'serialize' declared void".
It's my understanding that this won't work because the enable_if
is evaluated only after argument dependent lookup? Is that right?
For those that want to play around with this, I have the code up on repl.it: https://repl.it/repls/HalfBlandJumpthreading
There's two separate things going on in your example: there's (function) template argument deduction, and there's argument dependent lookup (ADL). The relationship between these two is slightly complex if you start trying to explicitly specify template parameters (hey, it's C++), you can read more here: http://en.cppreference.com/w/cpp/language/adl (in the notes section).
That said, in general in C++ it's typically better to allow function templates to deduce their arguments rather than specify them explicitly, which is what you were trying to do here anyway, so all is well.
When you do:
namespace lib1 {
template <typename T>
void archive(T & t)
{
serialize(t);
}
}
The call to serialize
qualifies for ADL, and since it depends on t it is deferred until the template is instantiated since the type of t
is required (this is called 2 phase lookup). When you call archive
with an object of type VectorInt
, the call to serialize
will look in the namespace of VectorInt
. Everything is working just fine. The problem is in this code:
template<typename T>
void serialize(std::enable_if<std::is_same<T, VectorInt>::value, T>::type & vect) {
std::cout << vect.x << std::endl;
}
You didn't specify template parameters explicitly, so they have to be deduced. But the form you give here does not allow deduction: http://en.cppreference.com/w/cpp/language/template_argument_deduction, see non-deduced contexts, the very first example. To try to understand better why, consider what you are asking the compiler to do: you are passing a VectorInt
and asking the compiler to find T
such that std::enable_if<std::is_same<T, VectorInt>::value, T>::type>
happens to be VectorInt
. This seems reasonable because intuitively enable_if
is just the identity operator (for types) if the first argument is true. But the compiler has no special knowledge of enable_if
. This is equivalent to saying: find T
such that Foo<T>::type
is Bar
. The compiler has no way to do this shy of instantiating Foo
for every single T, which is not possible.
We want to use enable_if, but not in a way that disables deduction. The best way to use enable_if
is typically in a defaulted template parameter:
template<typename T, typename U = typename std::enable_if<std::is_same<T, VectorInt>::value>::type >
void serialize(T& vect) {
std::cout << vect.x << std::endl;
}
U
isn't used for anything, but when serialize
is passed a VectorInt
, it will now deduce T
from the passed argument, and then it will deduce U which has a default. However, if the enable_if argument is false then U will not correspond to any type, and the instantiation is ill-formed: classic SFINAE.
This answer is already pretty long, but enable_if
itself is a reasonably deep topic; the form given above works here but doesn't work for disjoint sets of overloads. I'd suggest reading more about ADL, template argument deduction, SFINAE, and enable_if, outside of just SO (blog posts, Cppcon youtube videos, etc).