【问题标题】:Sfinae type trait to detect template function doesn't work with std::forward检测模板函数的 Sfinae 类型特征不适用于 std::forward
【发布时间】:2017-02-23 09:15:01
【问题描述】:

我最近尝试制作一个 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 的代码 sn-p:GCC, Clang

我的问题是: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");

【问题讨论】:

  • 取消注释 test(...) 并查看替换期间的实际错误是什么。这会有所帮助。
  • “不适用于 std::forward”是什么意思?没有一种类型是不能调用 std::forward 且编译时已知结果的类型。
  • 我认为这是clang中的一个错误。添加转发引用使其认为构造方法存在重载,因此会抛出错误。

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


【解决方案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&lt;T&gt;时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)...);
    }
};

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

【讨论】:

  • hmm... 你确定在正文中使用 forward 而不是在返回类型中应该有效吗?大多数情况下是的,但是如果它解析为不同的函数,是否会导致隐藏的错误或编译错误?
  • @GuillaumeRacicot:是的,我不太确定,这就是为什么我写了“但是,我现在不确定代码的正确性!!” :) 。但是,c++14 方式应该没有问题。我真的希望有人可以扩展 Clang 的问题。我找不到太多关于它的内容。
猜你喜欢
  • 2012-02-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-10-03
  • 2016-06-26
  • 2013-08-24
相关资源
最近更新 更多