Search code examples
c++c++11operator-overloadingvariadic-templatesambiguous

ambiguous operator[] in variadic template


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?


Solution

  • 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.