I'm trying to compile this example, where a variadic class template inherits from a variadic amount of bases, each of which implements a different operator[]
:
#include <iostream>
template <typename T>
struct Field {
typename T::value_type storage;
typename T::value_type &operator[](const T &c) {
return storage;
}
};
template<typename... Fields>
struct ctmap : public Field<Fields>... {
};
int main() {
struct age { typedef int value_type; };
struct last_name { typedef std::string value_type; };
ctmap<last_name, age> person;
person[last_name()] = "Smith";
person[age()] = 104;
std::cout << "Hello World!" << std::endl;
return 0;
}
When I compile with gcc (Debian 4.9.2-10), I get the following error
main.cpp: In function ‘int main()’:
main.cpp:22:23: error: request for member ‘operator[]’ is ambiguous
person[last_name()] = "Smith";
^
main.cpp:7:27: note: candidates are: typename T::value_type& Field<T>::operator[](const T&) [with T = main()::age; typename T::value_type = int]
typename T::value_type &operator[](const T &c) {
^
main.cpp:7:27: note: typename T::value_type& Field<T>::operator[](const T&) [with T = main()::last_name; typename T::value_type = std::basic_string<char>]
main.cpp:23:17: error: request for member ‘operator[]’ is ambiguous
person[age()] = 104;
^
main.cpp:7:27: note: candidates are: typename T::value_type& Field<T>::operator[](const T&) [with T = main()::age; typename T::value_type = int]
typename T::value_type &operator[](const T &c) {
^
main.cpp:7:27: note: typename T::value_type& Field<T>::operator[](const T&) [with T = main()::last_name; typename T::value_type = std::basic_string<char>]
Why is this ambiguous?
A portable way do do what you want is roughly:
template<class...Ts>
struct operator_index_inherit {};
template<class T0, class T1, class...Ts>
struct operator_index_inherit<T0, T1, Ts...>:
T0, operator_index_inherit<T1, Ts...>
{
using T0::operator[];
using operator_index_inherit<T1, Ts...>::operator[];
};
template<class T0>
struct operator_index_inherit<T0>:
T0
{
using T0::operator[];
};
then:
template<class... Fields>
struct ctmap : operator_index_inherit<Field<Fields>...> {
using base = operator_index_inherit<Field<Fields>...>;
using base::operator[];
};
here we linearly inherit from each of the types, and using operator[]
on our parents.
If we could using Field<Fields>::operator[]...;
we would not have to do this.
Some care has to be taken with constructors (which I did not take), but you might not need to do this.
live example.
What is actually going wrong depends on details of the standard I am less than certain of. Basically, you are mixing operators and inheritance and overloading in a complex way. Even if your code is standard compliant (which it may or may not be), it is compliant in a way that some compilers die on.
The code is invalid and gcc is correct to reject it (clang 3.6.0 accepts it though - this is a bug). The rules for looking up an operator start with, from [over.match.oper]:
[...] for a binary
operator @ with a left operand of a type whose cv-unqualified version is T1 and a right operand of a type
whose cv-unqualified version is T2, three sets of candidate functions, designated member candidates, non-member
candidates and built-in candidates, are constructed as follows:
— If T1 is a complete class type or a class currently being defined, the set of member candidates is the
result of the qualified lookup of T1::operator@
(13.3.1.1.1); otherwise, the set of member candidates
is empty.
The lookup rules for a member name are (since we're looking up ctmap<last_name,age>::operator[]
), from [class.member.lookup]:
The lookup set for f in C, called S(f,C), [...] is calculated
as follows:
If C contains a declaration of the name f, [...]
Otherwise (i.e., C does not contain a declaration of f or the resulting declaration set is empty), S(f,C) is
initially empty. If C has base classes, calculate the lookup set for f in each direct base class subobject Bi,
and merge each such lookup set S(f,Bi) in turn into S(f,C).
The following steps define the result of merging lookup set S(f,Bi) into the intermediate S(f,C):
— [...]
— Otherwise, if the declaration sets of S(f,Bi) and S(f,C) differ, the merge is ambiguous: the new
S(f,C) is a lookup set with an invalid declaration set and the union of the subobject sets. In subsequent
merges, an invalid declaration set is considered different from any other.
— [...]
Basically - we have two base classes here, both with an operator[]
. Both declaration sets differ - so the merge is ambiguous. The way to disambiguate would be to introduce using-declarations bringing in all the base class member functions into the derived class, so that the initial lookup set finds everything.
To shorten your example:
struct A { void foo(char) { } };
struct B { void foo(int ) { } };
struct C : A, B { };
struct D : A, B {
using A::foo;
using B::foo;
};
With that hierarchy
C c;
c.foo(4); // error: ambiguous lookup set for foo()
D d;
d.foo('x') // OK: calls A::foo()