4

Я пытаюсь собрать этот пример, в котором шаблон VARIADIC класс наследует от VARIADIC количества оснований, каждое из которых реализует различные operator[]:неоднозначным оператор [] в шаблоне VARIADIC

#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; 
} 

Когда я компилирую с GCC (Debian 4.9.2-10), я получаю следующую ошибку

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>] 

Почему это неоднозначно?

+1

Это принимается на лязг ++ 3.8: http://melpon.org/wandbox/permlink/huzwGp0kc2OafMZl (но я 'm не уверен, какой компилятор прав в этом случае) – dyp

+0

(В основном функции-члены в нескольких базовых классах не перегружаются. Я не уверен, как это взаимодействует с поиском оператора, хотя на первый взгляд кажется, что clang неверен.) – dyp

+0

Вы можете сделать это совместимым с линейным/t на основе наследования на основе ree и 'using'. – Yakk

ответ

3

Портативный способ делать делать то, что вы хотите, грубо:

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[]; 
}; 

затем:

template<class... Fields> 
struct ctmap : operator_index_inherit<Field<Fields>...> { 
    using base = operator_index_inherit<Field<Fields>...>; 
    using base::operator[]; 
}; 

здесь мы линейно унаследует от каждого из типов, и using operator[] на наших родителей.

Если бы мы могли using Field<Fields>::operator[]...;, нам не пришлось бы это делать.

Некоторая осторожность должна быть предпринята с конструкторами (которых я не принимал), но вам может и не понадобится это делать.

live example.

Что на самом деле происходит неправильно, зависит от деталей стандарта. Я меньше, чем некоторые из них. В принципе, вы смешиваете операторы, наследование и перегрузку сложным образом. Даже если ваш код соответствует стандарту (который он может или не может быть), он совместим таким образом, что некоторые компиляторы умирают.

+1

Я не понимаю, как это отвечает на вопрос tbh –

+0

WOW ... это работа, возможно, потребуется некоторое время, чтобы полностью понять ваш пример (я еще не полностью подготовлен на C++ 11), большое спасибо – Trungus

+0

@light true, но он решает проблему. – Yakk

2

Код недействителен, и gcc корректно отклоняет его (clang 3.6.0 принимает его, хотя - это ошибка). Правила для поиска оператора Начнем с того, из [over.match.oper]:

[...] для бинарной оператора @ с левым операндом такого типа, резюме-неквалифицированным версия T1 и правый операнд типа которого CV-неквалифицированные версии T2, три набора функций-кандидатов, обозначенная кандидатов в члены, , не являющихся членами кандидатов и встроенных кандидатов, строятся следующим образом:
- Если T1 является полным типом класса или классом, который в настоящее время определяется, набор кандидатов-кандидатов равен результат квалифицированного поиска T1::[email protected] (13.3.1.1.1); в противном случае набор кандидатов-кандидатов пуст.

Поиска правил для имени элемента являются (так как мы смотрим вверх ctmap<last_name,age>::operator[]), из [class.member.lookup]:

Множество подстановок для F в C, которая называется S (е, с), [...] вычисляется следующим образом:

Если C содержит объявление имени F, [...]

в противном случае (т.е., C не содержит объявления f или результирующий набор объявлений пуст), S (f, C) - изначально пуст. Если C имеет базовые классы, вычислите набор поиска для f в каждом подобъекте соответствующего прямого базового класса Bi, и объедините каждый такой набор поиска S (f, B i), в свою очередь, в S (f, C).

Следующие шаги определяют результат объединения множества поиска S (F, В я) в промежуточные S (F, C):
- [...]
- в противном случае, если декларация устанавливает из S (f, B) и S (f, C) отличаются, слияние неоднозначно: новый S (f, C) представляет собой набор поиска с недопустимым набором объявлений и объединением наборов подобъектов. В последующих сливается, недопустимый набор объявлений считается отличным от любого другого.
- [...]

В основном - у нас есть две базовые классы здесь, как с operator[]. Оба набора объявлений отличаются друг от друга, поэтому слияние неоднозначно. Путем устранения неоднозначности было бы ввести с использованием объявления s, введя все функции-члены базового класса в производный класс, чтобы исходный набор поиска нашел все.

Чтобы сократить свой пример:

struct A { void foo(char) { } }; 
struct B { void foo(int) { } }; 

struct C : A, B { }; 

struct D : A, B { 
    using A::foo; 
    using B::foo; 
}; 

С этой иерархии

C c; 
c.foo(4); // error: ambiguous lookup set for foo() 

D d; 
d.foo('x') // OK: calls A::foo() 
+0

Я понимаю .... У меня вопрос, как я могу назвать B :: foo()? ... Я пытаюсь использовать «4» или объявить переменную int, но всегда даю двусмысленный поиск. – Trungus

+0

@Trungus Аргументы не имеют значения - это фактический поиск имени 'foo', который терпит неудачу, потому что это неоднозначно. Чтобы вызвать его, вам нужно использовать * using-declaration * ('using B :: foo' в классе). Или вы могли бы называть его указателем на член ('(C {}. * & B :: foo) (4);' <== не писать этот код, я просто включаю его для полноты) – Barry

+0

correct me если я ошибаюсь, но ваш пример уже имеет «использование B: foo» в объявлении класса, почему, почему все еще есть двусмысленная ошибка поиска? – Trungus

Смежные вопросы