2014-02-15 109 views
1

在C++ 11中,如何專用一個使用decltype聲明「複雜」尾隨返回類型的函數模板?在GCC下面的作品,但產生「錯誤C2912:明確的專業化‘詮釋F(無效)’不是一個函數模板的一個特例」,在VC2013:具有decltype追蹤返回類型的專用函數模板

#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,和使用內置不依賴於任何函數的結果類型操作,正常工作與模板特:

自動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 ::對於一個不可改變的傳統容器類開始和std ::結束(從而提供基於範圍循環)如果這種專業化不起作用?

對於您所提供的代碼,一種解決方法是使用一個typedef:

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

所有三種主流的編譯器(GCC,鐺,VS)似乎很樂意與此代碼。


UPDATE:

我怎麼都專門std::beginstd::end (從而提供範圍爲基礎的循環)的一個不可改變的 遺留容器類,如果這種專業化的不工作?
[並從評論:] 我認爲專精std::beginstd::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

感謝typedef的把戲,這絕對是新的我!我仍然試圖理解C++ 11中關於std :: begin等的真正習慣用法。也許如果我的主要擔心是提供基於範圍的for循環,我不應該太在意std :: begin/std :: end並且只提供獨立的非std begin/end函數? –

+0

@ChristianHackl對於舊的C風格數組(例如'int v [42];'),獨立的'std :: begin()'和'std :: end()'方便。如果你想讓你的舊容器使用新的基於範圍的for循環,只需提供'my_container.begin()'和'my_container.end()'* member *函數。假設你可以更換舊容器,但是你寫的不可更改......嗯。或者,您可以爲這些舊容器定義視圖,而不需要更改舊代碼。無論如何,如果一個簡單的typedef足夠了,我會去typedef。 – Ali

+0

我正在考慮沿着同樣的路線...... LegacyContainerLoopAdapter用begin()/ end()成員函數包裝LegacyContainer。但是,當然,std :: begin/std :: end對於更高級的泛化類型也是很方便的(比如像bool empty(T const&c){return std :: begin(c)= = std :: end(c);})。這就是爲什麼我認爲專業std :: begin和std :: end永遠是最好的方法。 –

相關問題