Reputation: 10911
For some reason, I can see the top most template<typename T> X<...>::fn(T&&)
, but not the base class versions. Importing them using the using
keyword doesn't work. As I understand it:
In Class definition
Sample code:
#include <iostream>
#include <type_traits>
#define ENABLE_IF(...) std::enable_if_t<__VA_ARGS__, int> = 0
struct dummy {};
template<typename T>
struct always_false : std::false_type {};
template<typename...Ts>
struct X;
template<typename Tx, typename T, typename...Ts>
struct X<Tx, T, Ts...> : X<Tx, Ts...>
{
using X<Tx, Ts...>::fn;
template<typename R, ENABLE_IF(std::is_same<T, R>::value)>
auto fn(R&& x)
{
return x;
}
};
template<typename Tx>
struct X<Tx>
{
template<typename R>
auto fn(R&& x)
{
static_assert(always_false<R>::value, "Invalid type");
}
};
int main()
{
X<dummy, int, float> x;
std::cout << x.fn(1) << std::endl;
std::cout << x.fn(1.f) << std::endl;
std::cout << "Hello, world!\n";
}
I've tried this on g++, clang and VC++ and they all have various errors, (ambiguous call, member function disabled and could not deduce). It's interesting to note that g++ is failing on the call to X<dummy, int, float>::fn(int&&)
, whereas clang and VC++ are failing on calling X<dummy, int, float>::fn(float&&)
.
As I understand it, the compiler should ignore the absolute base class member function template<typename R> R X<dummy>::fn(R&&)
when calling X<dummy, int, float>::fn(float&&)
, because that template should resolve to float X<dummy>::fn(float&&)
which is an exact match to derived member function float X<dummy, float>::fn(float&&)
requiring the derived one to be called without ambiguity.
What am I doing wrong? What am I not understanding?
To paraphrase T.C.'s answer so far, "this is what the spec says", which I would say is not the correct interpretation. The two points given there are in conflict with each other. If they are an equally good match (point 1), then only the most derived function signature should be visible (point 2).
Anyway, if the problem is a spec problem, then it should go away if I were to disable the possibility of a matching overload which would result in an ambiguity. Thus the following should work:
#include <iostream>
#include <type_traits>
#define ENABLE_IF(...) std::enable_if_t<__VA_ARGS__, int> = 0
template<typename T>
struct always_false : std::false_type {};
template<typename...Ts>
struct list {};
template<typename...Ts>
struct is_one_of;
template<template <typename...> class TT, typename T, typename T1, typename...Ts>
struct is_one_of<T, TT<T1, Ts...>> : is_one_of<T, TT<Ts...>> {};
template<template <typename...> class TT, typename T, typename...Ts>
struct is_one_of<T, TT<T, Ts...>> : std::true_type {};
template<template <typename...> class TT, typename T>
struct is_one_of<T, TT<>> : std::false_type {};
template<typename...Ts>
struct X;
template<typename L, typename T, typename...Ts>
struct X<L, T, Ts...> : X<L, Ts...>
{
using X<L, Ts...>::fn;
template<typename R, ENABLE_IF(std::is_same<T, R>::value)>
constexpr auto fn(R&& x) const
{
return x;
}
};
template<typename L>
struct X<L>
{
template<typename R, ENABLE_IF(!is_one_of<R, L>::value)>
constexpr auto fn(R&& x) const
{
static_assert(always_false<R>::value, "Type R didn't match");
}
};
template<typename...Ts>
struct XX : X<list<Ts...>, Ts...> {};
int main()
{
XX<int, float> x;
std::cout << x.fn(1) << std::endl;
std::cout << x.fn(2.f) << std::endl;
}
It does work under g++, but has the same problems under clang and VC++. So, is g++ the only one that is conforming here and the rest are defective?
Upvotes: 0
Views: 873
Reputation: 137394
Two things:
Your baseline catch-all version of fn
is an equally good match as the other fn
varieties; therefore at best you'll get an ambiguous overload error.
Hiding for using-declarations does not consider the full signature (which for function templates would include the template parameter list). It only considers 1) name, 2) (function) parameter-type-list, 3) cv-qualification, and 4) ref-qualifier (if any). If all four match, the base class function template is hidden and not brought in by the using-declaration. Notably, the template parameter list is not considered. In your case, the only thing different between the various fn
s is the template parameter list; they all have the same name, same parameter-type-list, same cv-qualification, and same ref-qualifier (or lack thereof). Therefore, the most derived fn
will hide all fn
s from the base classes.
GCC appears to not implement this part to spec and consider the template parameter list when deciding on hiding.
One possible fix for this part is to move the enable_if
to a function parameter, which is considered by the hiding check.
The way overload resolution in C++ works is:
Hiding operates at the first step: if a declaration is hidden, it is not found by name lookup, and therefore it is not in the initial set of candidates and will not be considered by overload resolution under any circumstance, regardless of what happens in step 2, 3, or 4. A hidden declaration effectively doesn't exist for the purposes of overload resolution.
So, in your case, the base class fn
s are all hidden. What does it mean? It means that the only candidate found by name lookup is the int
one from the most derived class, nothing else. If template argument deduction and substitution succeed, that function template will be called. If they fail (as in the x.fn(2.f)
case), then there is no viable candidate left, and you get an error.
Upvotes: 3