How can I define a type trait that checks whether, for a type T
, the function ::foo(T)
is declared?
What I'm finding hard, is to have ::foo
in SFINAE friendly way. For instance, if the compiler has got to the point where the following is defined,
template<typename T>
void f(T t) { foo(t); }
it's prefectly fine if no foo
whatsover has been seen so far.
But as soon as I change foo
to ::foo
, then I get a hard error.
I have a customization point like this:
// this is in Foo.hpp
namespace foos {
inline constexpr struct Foo {
template <typename T, std::enable_if_t<AdlFooable<std::decay_t<T>>::value, int> = 0>
constexpr bool operator()(T const& x) const {
return foo(x);
}
} foo{};
}
that allows customizing the behavior of a call to foos::foo
by defining an ADL foo
overload for the desired type.
The definition of the trait is straightforward:
// this is in Foo.hpp
template <typename T, typename = void>
struct AdlFooable : std::false_type {};
template <typename T>
struct AdlFooable<T, std::void_t<decltype(foo(std::declval<T const&>()))>>
: std::true_type {};
Given that, if one defines
// this is in Bar.hpp
namespace bar {
struct Bar {};
bool foo(bar::Bar);
}
then a call to
// other includes
#include "Foo.hpp"
#include "Bar.hpp"
bool b = foos::foo(bar::Bar{});
works as expected, with foos::foo
routing the call to bar::foo(bar::Bar)
, which is found via ADL. And this works well regardless of the order of the two #include
s, which is good, because they might be included in either order, if // other includes
transitively includes them.
So far so good.
What I don't really like of this approach, is that one could mistakenly define, instead of the second snippet above, the following,
// this is in Bar.hpp
namespace bar {
struct Bar {};
}
bool foo(bar::Bar);
with foo
in global scope.
In this case, if the #include "Bar.hpp"
happens to come before #include "Foo.hpp"
, the code program will work, because the body of Foo::operator()
will pick ::foo(bar::Bar)
by ordinary lookup.
But as soon as the order of the #include
s happens to be reversed, then the code breaks.
Yes, the bug is in defining foo(bar::Bar)
in global namespace, but I think it's also a bug that it can pass unnoticed by pure chance.
That's why I would like to change the type trait to express that "an unqualified call to foo(T)
is found, but not via ordinary lookup", or, more directly, "foo(std::declval<T>())
must compile, but ::foo(std::declval<T>())
must not compile".
After I accepted the answer, I have re-read relevant parts of Josuttis' C++ Templates - The Complete Guide and found that ADL can be inhibited by a mean other than qualifying the function, i.e. wrapping the name of the unqualified function in parenthesis, e.g. while foo(args...)
undergoes ADL, (foo)(args...)
doesn't!
Unfortunately, that just means that the name foo
is looked up as if it was fully qualified (I presume with the current namespace), and so the attempt fails the same way as described above.
The ordinary way to do this is to call the customization implementation only via ADL. So the overload ::foo(bar::Bar)
is not considered in any case, regardless of include order.
And the way to do this is to include a poison pill:
namespace foos {
namespace detail {
void foo() = delete;
struct Foo {
template <typename T>
constexpr auto operator()(T const& x) const -> decltype(foo(x)) {
return foo(x);
}
};
}
inline constexpr detail::Foo foo{};
}
The ordinary lookup of foo
will find foos::detail::foo
which will not work. It cannot examine the global scope. Therefore, the foo
call will only lookup via ADL.
Edit:
This actually tells you how to form a trait that foo(x)
is only callable by ADL, the nominal question. And that is, if foos::foo(x)
is callable with this implementation, then foo(x)
is callable via ADL only.