2016-03-17 3 views
9

Следующий код представляет собой пример учебной реверсивной перегруженной функции. В обоих лязгом и GCC, он компилируется и main возвращает 36 (как и ожидалось):Является ли переменная шаблона переменной C++ 11 переменной переменной неоднозначной?

template <typename T> 
int add(T val) 
{ 
    return val; 
} 

template <typename FirstTypeT, typename... RestT> 
int add(FirstTypeT first_value, RestT... rest) 
{ 
    return first_value + add<RestT...>(rest...); 
} 

int main(void) 
{ 
    return add(12, 12, 12); 
} 

Однако, здесь есть небольшая модификация. Он использует зависимый тип в определении шаблона вместо параметра шаблона непосредственно:

struct Foo 
{ 
    using SomeType = int; 
}; 

template <typename T> 
int add(typename T::SomeType val) 
{ 
    return val; 
} 

template <typename FirstT, typename... RestT> 
int add(typename FirstT::SomeType first_value, typename RestT::SomeType... rest) 
{ 
    return first_value + add<RestT...>(rest...); 
} 

int main(void) 
{ 
    return add<Foo, Foo, Foo>(12, 12, 12); 
} 

Это компилирует и работает как задумано с использованием GCC 5.2, но fails с помощью лязг 3.8:

clang++ variadic.cpp -o var -std=c++11 -Wall 
variadic.cpp:15:26: error: call to 'add' is ambiguous 
    return first_value + add<RestT...>(rest...); 
         ^~~~~~~~~~~~~ 
variadic.cpp:15:26: note: in instantiation of function template specialization 'add<Foo, Foo>' requested here 
    return first_value + add<RestT...>(rest...); 
         ^
variadic.cpp:20:12: note: in instantiation of function template specialization 'add<Foo, Foo, Foo>' requested here 
    return add<Foo, Foo, Foo>(12, 12, 12); 
     ^
variadic.cpp:7:5: note: candidate function [with T = Foo] 
int add(typename T::SomeType val) 
    ^
variadic.cpp:13:5: note: candidate function [with FirstT = Foo, RestT = <>] 
int add(typename FirstT::SomeType first_value, typename RestT::SomeType... rest) 
    ^
1 error generated. 

Мой вопрос двоякий ,

  1. Является ли это на самом деле является допустимым использование параметра шаблона пакета TYPENAME применить оператор разрешения области видимости для каждого члена стаи, как в typename RestT::SomeType...?
  2. Является ли clang правильным по отношению к стандарту, или это ошибка? Является ли второй пример действительно более неоднозначным, чем первый? (Для первого примера, кажется, что вы могли бы сказать, что единственная перегрузка аргумента неоднозначная с вторым экземпляром с RestT = <>)
+1

MSVC15 компилирует оба образца. Кланг здесь странный, но у меня нет стандартной ссылки, чтобы сказать, кто прав, а кто не прав. – Niall

+2

Исходя из этого дефекта ([CWG1395] (http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#1395)), я думаю, что это ошибка clang. Я не могу найти ничего о том, почему встроенный тип изменит ситуацию. – Niall

+1

Частичное упорядочение здесь сложно, так как все находится в невыводимом контексте. –

ответ

7
  1. Да, это нормально.
  2. Текущая формулировка совершенно понятна: пакет параметров полностью игнорируется во время частичного упорядочения, потому что для него нет аргументов ([temp.deduct.partial]/(3.1)). [Temp.func.order]/5 также дает очень на точке пример, даже с аргументами выводимых шаблонов - указывая, что ваш первый пример также неоднозначна:

    [Примечание: С частичной упорядоченности в контексте вызова учитывает только параметры, для которых существуют явные вызовы, некоторые параметры игнорируются (а именно, пакеты параметров функций, параметры с аргументами по умолчанию, и параметры многоточия). [...] [Пример:

    template<class T, class... U> void f(T, U ...); // #1 
    template<class T   > void f(T  ); // #2 
    
    void h(int i) { 
        f(&i); // error: ambiguous 
        // [...] 
    } 
    

    Однако, это не является оптимальным.Существует core issue 1395 на VARIADIC шаблона частичного упорядочении:

    РГС решила, что пример должен быть принят, обращением этого случая как поздний тай-брейк, предпочитая пропущенный параметр над параметром пакетом.

    (Issue 1825) дает более четкую стратегию.) Оба компилятора реализуют это правило для первого случая; Только GCC делает для второго (т. Е. Можно считать на полшага вперед).

+0

Хм, я полагаю, что clang реализует направление 1395, так как это нормально с выведенным контекстным случаем. Возможно, разница в том, что она не реализует 1391? –

+0

@ T.C. Разрешение 1391 противоречиво и, вероятно, плохо. Это не оправдано. – Columbo

+0

Спасибо за ваш ответ, это именно то, что я искал. Я, вероятно, зарегистрирую ошибку с Clang, так как кажется, что они намереваются реализовать 1395 (например, этот отчет об ошибке https://llvm.org/bugs/show_bug.cgi?id=14372) –

0

Сообщения об ошибке уже показало причину.

При создании add (12) есть две доступные функции шаблона. Это

template <typename T> 
int add(typename T::SomeType val); 

и

template <typename FirstT, typename... RestT> 
int add(typename FirstT::SomeType first_value, typename RestT::SomeType... rest); 
// and RestT is empty here(RestT = <>) 

Это не стандартное применение и лязг правильно.

Рассмотрите этот код.

#include <tuple> 
#include <type_traits> 

struct Foo 
{ 
    using SomeType = int; 
}; 

// helper function to sum a tuple of any size 
template<typename Tuple, std::size_t N> 
struct TupleSum { 
    typedef typename std::tuple_element<N - 1, Tuple>::type ref_t; 
    typedef typename std::remove_reference<ref_t>::type noref_t; 

    static noref_t sum(const Tuple& t) 
    { 
     return std::get<N - 1>(t) + TupleSum<Tuple, N - 1>::sum(t); 
    } 
}; 

template<typename Tuple> 
struct TupleSum<Tuple, 1> { 
    typedef typename std::tuple_element<0, Tuple>::type ref_t; 
    typedef typename std::remove_reference<ref_t>::type noref_t; 

    static noref_t sum(const Tuple& t) 
    { 
     return std::get<0>(t); 
    } 
}; 

template <typename... RestT> 
int add(typename RestT::SomeType... rest) { 
    typedef decltype(std::forward_as_tuple(rest...)) tuple_t; 
    return TupleSum<tuple_t, sizeof...(RestT) >::sum(std::forward_as_tuple(rest...)); 
} 

int main(void) 
{ 
    return add<Foo, Foo, Foo>(12, 12, 12); 
} 
+1

Спасибо за ответ. Конечно, сообщение об ошибке объясняет предполагаемую проблему. Но, как я упоминал в вопросе, почему второй пример неоднозначен, а первый не в этом случае? И почему GCC принимает его? –

+0

Хорошо, прочитав ваше редактирование, похоже, вы думаете, что это ошибка GCC. Но в первом примере одна и та же кажущаяся двусмысленная перегрузка (и многие другие примеры вариационных функций). Так это тоже недействительно? –

+1

Извините, я неправильно понимаю ваш вопрос. Я прочитал [N2242] (http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2007/n2242.pdf) и [N2555] (http://www.open-std.org /jtc1/sc22/wg21/docs/papers/2008/n2555.pdf), и ничего не нашел. Эти два примера также работают в MSVC 14. но я думаю, что это все еще устаревший способ сделать это, потому что std :: integer_sequence в C++ 14 изобретен как инструмент для решения подобных проблем. – owent

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