2017-06-06 84 views
16

檢測成語的工作原理如下爲什麼sfinae如果constexpr不允許?

template<typename T, typename = void> 
struct has_foo {static constexpr bool value = false;}; 
template<typename T> 
struct has_foo<T, std::void_t<decltype(&T::foo)>> {static constexpr bool value = true;}; 
template<typename T> 
constexpr bool has_foo_v = has_foo<T>::value; 

,然後我們可以檢測到任何類型的Tfoo存在。

if constexpr(has_foo_v<decltype(var)>) 
    var.foo(); 

我的問題是,這是相當多輸入(讀:想砸我的鍵盤大量輸入),我想知道是否以下是可能的

if constexpr(std::void_t<decltype(&decltype(var)::foo)>(), true) 
    var.foo(); 

這是不。

背後有一個原因嗎?
更具體地說,如果這是允許的話,必須做出什麼樣的折衷?

+9

[相關](https://meta.stackoverflow.com/a/323382/2069064)。一個答案可能是因爲這裏沒有替代,所以SFINAE如何?另一個答案可能是因爲它沒有被考慮,因爲提案總是關於'如果'採取'bool'? – Barry

+0

@Barry希望將問題編輯成可以有明確答案的表單。原來是想[順着這樣的答案](https://stackoverflow.com/a/6623089/4832499) –

回答

6

您在使用指針的成員函數是一個壞主意;如果foo被重載,它虛假地失敗(你有一個foo,但不只是一個)。誰真的想要「你有一個富有」?幾乎沒有人。

這裏是一個簡短的版本:

template<class T> 
using dot_foo_r = decltype(std::declval<T>().foo()); 

template<class T> 
using can_foo = can_apply<dot_foo_r, T>; 

其中

namespace details { 
    template<template<class...>class, class, class...> 
    struct can_apply:std::false_type{}; 
    template<template<class...>class Z, class...Ts> 
    struct can_apply<Z, std::void_t<Z<Ts...>>, Ts...>:std::true_type{}; 
} 
template<template<class...>class Z, class...Ts> 
using can_apply = details::can_apply<Z, void, Ts...>; 

現在,寫dot_foo_r是有點討厭。

隨着constexpr lambda我們可以使它更少討厭,並做到內聯。

#define RETURNS(...) \ 
    noexcept(noexcept(__VA_ARGS__)) \ 
    -> decltype(__VA_ARGS__) \ 
    { return __VA_ARGS__; } 

它確實需要RETURNS宏,至少要等到@巴里的提交[](auto&&f)RETURNS(f())相當於[](auto&&f)=>f()

然後,我們寫can_invoke_f,這是一個constexpr變種std::is_invokable

template<class F> 
constexpr auto can_invoke(F&& f) { 
    return [](auto&&...args)->std::is_invokable<F(decltype(args)...)>{ 
    return {}; 
    }; 
} 

這給我們:

if constexpr(
    can_invoke([](auto&&var) RETURNS(var.foo()))(var) 
) { 
    var.foo(); 
} 

或使用@巴里提出的C++ 20語法:

if constexpr(can_invoke(var=>var.foo())(var)) { 
    var.foo(); 
} 

我們完成了。

訣竅是,RETURNS宏(或=> C++ 20功能)讓我們對錶達式執行SFINAE。拉姆達變成了一種簡單的方式來表達這個表達式作爲一個值。

你可以寫

[](auto&&var) ->decltype(var.foo()) { return var.foo(); } 

,但我認爲RETURNS是值得的(我不喜歡宏)。

+2

只考慮包括這種情況作爲下一個草案中另一種激勵的例子。 '如果constexpr(can_invoke_f (x => x.foo())){...}'與OP所要求的相比仍然不太理想,但並不差。 – Barry

+0

@Barry擺脫了「decltype」並改進了'can_invoke'; 'can_invoke'現在接受一個函數實例'f'並在'f'上返回'constexpr' invoke-tester。弗雷爾自由偷取/適應;我認爲現在是非常輕鬆的。 – Yakk

+3

什麼是'=>'語法? –

12

由於C++ 17總是有constexpr拉姆達的解決方法,如果你真的需要做SFINAE在線:

#include <utility> 

template <class Lambda, class... Ts> 
constexpr auto test_sfinae(Lambda lambda, Ts&&...) 
    -> decltype(lambda(std::declval<Ts>()...), bool{}) { return true; } 
constexpr bool test_sfinae(...) { return false; } 

template <class T> 
constexpr bool bar(T var) { 
    if constexpr(test_sfinae([](auto v) -> decltype(v.foo()){}, var)) 
     return true; 
    return false; 
} 

struct A { 
    void foo() {} 
}; 

struct B { }; 

int main() { 
    static_assert(bar(A{})); 
    static_assert(!bar(B{})); 
} 

[live demo]

+0

請注意,'declval ()'是一個左值引用,所以如果你測試的函數不接受非const的左值引用,測試將失敗。 –

+0

@IgorR。你是對的,但我們有創造lambda'test_sfinae'將會測試的冒險。不過,我不得不考慮它,因爲它遠非完美。 –

+1

@IgorR。我在'declval'中刪除了引用,現在取決於lambda創建者,如果他想使用引用或值。如果複製構造函數被顯式刪除,lambda將僅在接受引用時匹配,例如'[](auto&v)','[](const auto&v)'或'[](auto && v)' –

1

您還可以使用std::is_detected來減少代碼量。

在你的榜樣,代碼則看起來像:

template <class T> 
using has_foo_t = decltype(std::declval<T>().foo()); 

if constexpr(std::is_detected_v<has_foo_t,decltype(var)>) 
    var.foo(); 
+0

它的工作原理! :)雖然你確定這是C++ 17功能嗎? –

+1

@ W.F .:這是一個實驗性功能。它還沒有標準化。 – AndyG

相關問題