2017-07-31 98 views
0

這些天我在用SFINAE做實驗,還有一些讓我感到困惑。爲什麼my_type_a不能在my_function的實例化中推導出來?SFINAE和模板函數實例化:爲什麼在使用具有SFINAE的類型的函數參數中使用模板參數時不能推導出來?

class my_type_a {}; 

template <typename T> 
class my_common_type { 
public: 
    constexpr static const bool valid = false; 
}; 

template <> 
class my_common_type<my_type_a> { 
public: 
    constexpr static const bool valid = true; 
    using type = my_type_a; 
}; 

template <typename T> using my_common_type_t = typename my_common_type<T>::type; 

template <typename T, typename V> 
void my_function(my_common_type_t<T> my_cvalue, V my_value) {} 

int main(void) { 
    my_function(my_type_a(), 1.0); 
} 

G ++給了我這樣的:

/home/flisboac/test-template-template-arg-subst.cpp: In function ‘int main()’: 
/home/flisboac/test-template-template-arg-subst.cpp:21:30: error: no matching function for call to ‘my_function(my_type_a, double)’ 
    my_function(my_type_a(), 1.0); 
          ^
/home/flisboac/test-template-template-arg-subst.cpp:18:6: note: candidate: template<class T, class V> void my_function(my_common_type_t<T>, V) 
void my_function(my_common_type_t<T> my_type, V my_value) {} 
     ^~~~~~~~~~~ 
/home/flisboac/test-template-template-arg-subst.cpp:18:6: note: template argument deduction/substitution failed: 
/home/flisboac/test-template-template-arg-subst.cpp:21:30: note: couldn't deduce template parameter ‘T’ 
    my_function(my_type_a(), 1.0); 
          ^

我所期待的是,在調用my_function當我在main一樣,T會被推斷爲函數的第一個參數的類型,以及該類型將用於函數的實例化。但似乎my_common_type_t<T>是函數之前實例化,但即使如此,中my_cvalue類型將成爲my_type_a反正,所以我不明白爲什麼這是行不通的......

是否有不同的方式來做到這一點?我應該避免兩個(或更多)級別的模板間接?

+0

在'my_common_type :: type'中,'T'在[非推導的上下文](http://en.cppreference.com/w/cpp/language/template_argument_deduction#Non-deduced_contexts)中。你希望編譯器用每個可能的類型'T'實例化'my_common_type',希望其中的一個'my_common_type :: type'出來與'my_type_a'兼容;或者參與定理證明練習以嘗試並通過分析找到這種類型。編譯器不會。 –

+0

@Igor我明白規則1(來自您提供的鏈接)當然符合我的例子。但是,爲什麼不清楚'T'不一定是'my_type_a'?如果'my_common_type '在實例化'my_function'之前被實例化,那麼類型可能是'my_type_a'或者什麼也不是(因此函數將通過SFINAE消除)。如果在編譯期間或之後實例化,編譯器會將'my_common_type '的信息作爲候選(因此,'T = my_type_a'),是不是? –

+0

*「爲什麼不清楚」*這是我說的定理證明練習。這裏的「清除」意味着「可以從現有事實中證明」。也許它可以 - 但編譯器不需要擁有這種推理引擎。 –

回答

2

那麼,考慮一下:

template <> 
struct my_common_type<int> { 
    constexpr static const bool valid = true; 
    using type = my_type_a; 
}; 

template <> 
struct my_common_type<double> { 
    constexpr static const bool valid = true; 
    using type = my_type_a; 
}; 

// ... 

int main(void) { 
    my_function(my_type_a{}, 1.0); 
} 

該編譯器選擇my_common_type<int>my_common_type<double>

如果語言允許在您的情況下扣除,它將必須匹配將在my_common_type<T>::type爲了產生您發送到函數參數的確切類型。顯然,這不僅是不可能的,但以上面的例子來說,它可能有多種選擇!

幸運的是,有一種方法可以告訴編譯器my_common_type<T>將始終屈服於T。技巧的基本原理是這樣的:

template<typename T> 
using test_t = T; 

template<typename T> 
void call(test_t<T>) {} 

int main() { 
    call(1); 
} 

什麼是T推演?簡單!編譯器對這種匹配很滿意。此外,由於test_t不能專業化,因此test_t<soxething>僅爲something

template<typename T> 
using test_t = T; 

template<typename T> 
using test2_t = test_t<T>; 

template<typename T> 
void call(test2_t<T>) {} 

int main() { 
    call(1); // will also work 
} 

我們可以將此到你的情況,但我們需要一些工具:

template<typename T, typename...> 
using first_t = T; 

這是一樣的容易

此外,這與別名的多層次的工作壓力太大如上所述匹配,但我們也可以發送一些不會被使用的參數。我們將在這個未使用的包中製作sfinae。

現在,改寫my_common_type_t仍然是一件容易的比賽,而在未使用的包添加約束:

template <typename T> 
using my_common_type_t = first_t<T, typename my_common_type<T>::type>; 

注意,這也是工作:

template <typename T> 
using my_common_type_t = first_t<T, std::enable_if_t<my_common_type<T>::valid>>; 

現在扣將發生,因爲預期!Live (GCC)Live (Clang)

注意,這一招將與C++ 14在這種情況下,只有工作,因爲SFINAE(丟棄參數)時,才能保證,因爲C++ 14的情況發生。

另請注意,您應該爲您的特徵使用struct,或者使用public:來公開成員my_common_type<T>::type,否則GCC將輸出一個僞造錯誤。

+0

哇,這當然是一個不錯的選擇,它回答了我的問題!但是我的情況有點複雜,因爲'my_common_type :: type'是根據模板參數選擇的。換句話說,這不僅僅是一個啓用的問題。另外,我認爲編譯器會首先查看函數調用站點,並且看到'T'是'my_type_a',而不管'my_common_type'的特殊性。此外,我認爲'my_common_type '和'my_common_type '都不會被選中,因爲它們都不是'my_common_type '的專用於'T = my_type_a'的選項。 –

+1

我想更新我的問題,但是您的回答非常有用,我想再提一個問題來回答我的具體問題。 –

+0

@FlávioLisbôa謝謝!總是樂於提供幫助。問另一個問題是在這個網站確實做到這一點的正確方法。 –

相關問題