2014-02-15 4 views
1

В C++ 11, как я могу специализировать шаблон функции, который объявлен с помощью «сложного» типа возвращаемого возврата с помощью decltype? Следующие работы в НКУ, но производит «C2912 ошибки: явная специализация„ИНТ е (аннулируется)“не является специализация шаблона функции» в VC2013:Специализированный шаблон функции с типом возврата возвращаемого типа

#include <iostream> 

int myint() { return 1; } 

template<class T> 
auto f() -> decltype(myint()) // this seems to cause problems 
{ 
    std::cout << "general\n"; 
    return 1; 
} 

template <> 
auto f<double>() -> decltype(myint()) 
{ 
    std::cout << "special\n"; 
    return 2; 
} 

int main() 
{ 
    f<int>(); 
    f<double>(); // compiler error in VC, but not in GCC 
} 

Я говорю «сложный» в отсутствии технически точный потому что я не уверен, что делает разницу. Например, следующий decltype, используя встроенный операции не зависит от какого-либо типа результата функции, хорошо с шаблоном специализации работы:

Auto F() -> decltype (1 + 1)

Так , мои вопросы (все связанные друг с другом):

  1. Действительно ли мой код C++ 11?
  2. Это ошибка VC?
  3. Как я мог специализироваться на std :: begin и std :: end (и, следовательно, на основе диапазона для циклов) для неизменяемого старого класса контейнеров, если эта специализация не работает?

ответ

5

Является ли мой код правильным C++ 11?

Выглядит правильно. Кроме того, компилируется как с gcc, так и с clang с -Wall -Wextra.

Является ли это ошибкой VC?

Скорее всего. VC является пресловутым в этом отношении, см., Например, What exactly is "broken" with Microsoft Visual C++'s two-phase template instantiation? или google msvc two-phase lookup.

Как я мог специализироваться на std :: begin и std :: end (и, следовательно, на основе диапазона для циклов) для неизменяемого старого класса контейнеров, если эта специализация не работает?

Для кода вы предоставили, обходной путь будет использовать ЬурейеЕ:

#include <iostream> 

int myint() { return 1; } 

typedef decltype(myint()) return_type; 

template<class T> 
return_type f() 
{ 
    std::cout << "general\n"; 
    return 1; 
} 

template <> 
return_type f<double>() 
{ 
    std::cout << "special\n"; 
    return 2; 
} 

int main() 
{ 
    f<int>(); 
    f<double>(); 
} 

Все три основные компиляторы (НКУ, лязг, против), кажется, чтобы быть счастливым с этим кодом.


UPDATE:

Как я мог специализироваться std::begin и std::end (и, таким образом, обеспечивают диапазон на основе для петель) для неизменного унаследованного класса контейнера, если такого рода специализации не Работа?
[И по комментариям:] Я думал, что специализация std::begin и std::end всегда была лучшим подходом.

После придав ему некоторые мысли, специализируясь std::begin() и std::end() будет моим последним средством. Моя первая попытка состояла бы в том, чтобы предоставить функции begin() и end(); к сожалению, это не вариант для вас, потому что вы не можете изменить соответствующий код. Тогда, моя вторая попытка будет предоставлять бесплатные функции в моем собственном пространстве имен:

#include <iostream> 
#include <initializer_list> 
#include <vector> 

namespace my_namespace { 

template <typename T> class my_container; 
template <typename T> T* begin(my_container<T>& c); 
template <typename T> T* end(my_container<T>& c); 

template <typename T> 
class my_container { 

    public: 

    explicit my_container(std::initializer_list<T> list) : v(list) { } 

    friend T* begin<>(my_container& c); 
    friend T* end<>(my_container& c); 

    private: 

    std::vector<T> v; 
}; 

template <typename T> 
T* begin(my_container<T>& c) { 

    return c.v.data(); 
} 

template <typename T> 
T* end(my_container<T>& c) { 

    return c.v.data()+c.v.size(); 
} 

} 

int main() { 

    my_namespace::my_container<int> c{1, 2, 3}; 

    for (int i : c) 
    std::cout << i << '\n'; 
} 

Этот подход должен работать, если вы были в состоянии специализироваться std::begin() и std::end() для контейнера. Он также работает, если вы делаете это в глобальном пространстве имен (т. Е. Вы просто опускаете namespace my_namespace { и закрываете }), но я предпочел включить свою реализацию в свое собственное пространство имен.

Смотрите также

+0

Спасибо за ЬурейеЕ трюк, который был определенно новым для меня! Я все еще пытаюсь понять, что действительно идиоматично в C++ 11 относительно std :: begin et al. Возможно, если моя главная проблема заключается в предоставлении циклов, основанных на диапазонах, мне все равно не нужно беспокоиться о std :: begin/std :: end и просто предоставлять автономные функции запуска/завершения без функции std? –

+0

@ChristianHackl Свободностоящие 'std :: begin()' и 'std :: end()' удобны для старых массивов стилей C (например, 'int v [42];'). Если вы хотите, чтобы ваши старые контейнеры работали с новыми циклами на основе циклов, просто предоставьте функции 'my_container.begin()' и 'my_container.end()' * member *. Предполагая, что вы можете изменить старые контейнеры, но вы пишете unchangable ... Хм. Кроме того, вы можете определить представления для старых контейнеров, которые не требуют изменения старого кода. В любом случае, если достаточно простого typedef, я бы пошел с typedef. – Ali

+0

Я думал о чем-то в том же духе ... LegacyContainerLoopAdapter wrapping LegacyContainer с функциями-членами begin()/end(). Но тогда, конечно, std :: begin/std :: end также представляется удобным в качестве основных операций для обобщенных более высоких уровней (например, что-то вроде bool empty (T const & c) {return std :: begin (c) = = std :: end (c);}). Вот почему я думал, что специализация std :: begin и std :: end всегда были лучшим подходом. –

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