7

Another question of type "who's right between g++ and clang++?" for C++ standard gurus.

Suppose we want apply SFINAE to a variable template to enable the variable only if the template type fulfill a certain condition.

By example: enable bar if (and only if) the template type has a foo() method with a given signature.

Using SFINAE through an additional template type with default value

template <typename T, typename = decltype(T::foo())>
static constexpr int bar = 1;  

works for both g++ and clang++ but has a problem: can be hijacked explicating the second template type

So

int i = bar<int>;

gives a compilation error where

int i = bar<int, void>;

compile without problem.

So, from the bottom of my ignorance about SFINAE, I've tried enabling/disabling the type of the same variable:

template <typename T>
static constexpr decltype(T::foo(), int{}) bar = 2; 

Surprise: this works (compile) for g++ but clang++ doesn't accept it and give the following error

tmp_003-14,gcc,clang.cpp:8:30: error: no member named 'foo' in 'without_foo'
static constexpr decltype(T::foo(), int{}) bar = 2;
                          ~~~^

The question, as usual, is: who's right ? g++ or clang++ ?

In other words: according the C++14 standard, SFINAE can be used over the type of a variable template ?

The following is a full example to play with

#include <type_traits>

// works with both g++ and clang++
//template <typename T, typename = decltype(T::foo())>
//static constexpr int bar = 1;

// works with g++ but clang++ gives a compilation error
template <typename T>
static constexpr decltype(T::foo(), int{}) bar = 2;

struct with_foo
 { static constexpr int foo () { return 0; } };

struct without_foo
 { };

template <typename T>
constexpr auto exist_bar_helper (int) -> decltype(bar<T>, std::true_type{});

template <typename T>
constexpr std::false_type exist_bar_helper (...);

template <typename T>
constexpr auto exist_bar ()
 { return decltype(exist_bar_helper<T>(0)){}; }

int main ()
 {
   static_assert( true == exist_bar<with_foo>(), "!" );
   static_assert( false == exist_bar<without_foo>(), "!" );
 }
4

1 回答 1

-1

让我们分析一下这里发生了什么:

我最初的假设是,这看起来像是对 clang 的误解。当没有正确解析时,它无法返回解析树bar

首先,要确定问题出在 中bar,我们可以这样做:

template <typename T>
constexpr auto exist_bar_helper(int) -> decltype(void(T::foo()), std::true_type{});

它工作正常(SFINAE 正在做它的工作)。

现在,让我们更改您的代码以检查嵌套的失败解析是否被外部 SFINAE 上下文包装。改成bar函数后:

template <typename T>
static constexpr decltype(void(T::foo()), int{}) bar();

它仍然可以正常工作,很酷。然后,我会假设我们内部的任何不正确的解析decltype都会返回并使函数解析为 SFINAE 回退(std::false_type)......但不是。

这就是 GCC 所做的:

exist_bar -> exists_bar_helper -> bar (woops) -> no worries, i have alternatives
          -> exists_bar_helper(...) -> false

这就是 CLANG 所做的:

exist_bar -> exists_bar_helper -> bar (woops) // oh no
// I cannot access that var, this is unrecoverable error AAAAAAAA

它非常重视它,以至于忘记了上层上下文中的后备。

长话短说:不要在模板变量上使用 SFINAE,SFINAE 它本身就是一个编译器破解,当编译器试图“太聪明”时,它可能会以奇怪的方式表现

于 2018-02-12T13:52:54.263 回答