用于检测模板功能的Sfinae类型特征不适用于std :: forward

时间:2016-10-14 03:55:31

标签: c++ c++11 templates clang sfinae

我最近尝试制作一个sfinae类型特征来检测一个类是否包含一个名为construct的特定模板静态函数。

我带来了这个实现:

template<typename T, typename... Args>
struct has_template_construct_helper {
private:
    template<typename U, typename... As>
    static std::true_type test(decltype(&U::template construct<As...>)*);

    template<typename...>
    static std::false_type test(...);

public:
    using type = decltype(test<T, Args...>(nullptr));
};

template<typename T, typename... Args>
using has_template_construct = typename has_template_construct_helper<T, Args...>::type;

我认为那没关系,而且确实如此。我尝试用gcc和clang测试我的特性:

struct TestStruct {
    template<typename... Args>
    static auto construct(int a, double b, Args... args) -> decltype(std::make_tuple(a, b, args...)) {
        return std::make_tuple(1, 2.3, std::forward<Args>(args)...);
    }
};

// didn't fire! Hurrah!
static_assert(has_template_construct<TestStruct, std::string>::value, "Don't pass the test");

它适用于两个编译器。

但是,只要我添加转发引用,clang就会开始抱怨:

struct TestStruct {
    template<typename... Args>
    static auto construct(int a, double b, Args&&... args) -> decltype(std::make_tuple(a, b, std::forward<Args>(args)...))
    {
        return std::make_tuple(1, 2.3, std::forward<Args>(args)...);
    }
};

// fires on clang :(
static_assert(has_template_construct<TestStruct, std::string>::value, "Don't pass the test");

此处为coliru上的代码段:GCCClang

我的问题是:GCC和Clang之间哪一个是错误的,我如何修复我的代码以使其适用于两个编译器?

好的,我尝试过的东西,现在我更加困惑了。使用std::declval时,它会在clang中恢复工作!

struct TestStruct {
    template<typename... Args>
    static auto construct(int a, double b, Args&&... args) -> decltype(std::make_tuple(a, b, std::declval<Args>()...))
    {
        return std::make_tuple(1, 2.3, std::forward<Args>(args)...);
    }
};

// uh?? Works in clang?
static_assert(has_template_construct<TestStruct, std::string>::value, "Don't pass the test");

1 个答案:

答案 0 :(得分:1)

我不确定为什么你的代码在clang ++中失败(或传入g ++)。但这是一个更容易的选择。

#include <type_traits>
#include <tuple>
#include <string>

template <typename... T>
using void_t = void;

class Stat {
public:
    template <typename... T>
    static auto construct(int a, double b, T&&... t) ->
      decltype(std::make_tuple(1, 2.3, t...))
    {
      return std::make_tuple(1, 2.3, std::forward<T>(t)...);
    }
};

template <typename Class, typename... Args>
constexpr auto does_have_construct(int)
    -> decltype(&Class::template construct<Args...>, true)
{
    return true;
}

template <typename Class, typename... Args>
constexpr bool does_have_construct(long) { return false; }

class Stat2 {};

int main() {
    static_assert(does_have_construct<Stat, std::string>(0), "Nope!");

    return 0;
}

在返回类型扣除的decltype中指定std::forward<T>时,Clang特别不满意。如果我们删除它,就没有问题。 ,我现在不确定代码是否正确!!

在C ++ 14中,您可以将class Stat重写为:

class Stat {
public:
    template <typename... T>
    static auto construct(int a, double b, T&&... t)
    {
      return std::make_tuple(1, 2.3, std::forward<T>(t)...);
    }
};

正如您所看到的,在这种情况下,我们不必采取额外步骤来欺骗编译器。