为什么 `has_construct<Alloc, T, Args...>::value` 在 gcc (false) 和 clang (true) 上给出不同的值?

Why does `has_construct<Alloc, T, Args...>::value` give different values on gcc (false) and clang (true)?

我正在尝试实现allocate_traits,然后我尝试了很多方法,最终使它与gcc(>=5)兼容,下面是class做的判断关于 allocate_traits::construct

template <typename ...>
using type_helper = void;

template <typename T, typename... Args>
struct has_construct_helper {
    template <typename Alloc, typename = type_helper<
        decltype(declval<Alloc &>().construct
                 (declval<T *>(), declval<Args>()...))
    >> static constexpr true_type test(Alloc, int);

    template <typename Alloc> static constexpr false_type test(Alloc, ...);
};

template <typename Alloc, typename T, typename... Args>
using has_construct =
decltype(has_construct_helper<T, Args...>::test(declval<Alloc>(), 0));

并且我使用不包含 construct 的分配器对其进行了测试,它与 gcc (>= 5) 配合使用效果很好,但 clang (>= 3.7) 给我 true。此代码有效吗?

有一整个文件有同样的错误,可能更简单: https://gist.github.com/anonymous/9a6125c4796d4c0227cb

问题出在您的 type_helper 别名模板上。对于如何处理未使用的可变参数模板参数,标准中曾经存在歧义。 Clang 决定不理会它们,因此您的 decltype 表达式从未真正被检查过,因此 SFINAE 不会发生。

简单的解决方法是推迟到另一个特征,以便评估参数:

template <typename...> struct voider { using type = void; };
template <typename...Ts> using type_helper = typename voider<Ts...>::type;

这是您的代码的一个稍微简单的版本,它使用 SFINAE 的检测习惯用法。

template <typename...> struct voider { using type = void; };
template <typename...Ts> using void_t = typename voider<Ts...>::type;

template <typename Alloc, typename T, typename, typename... Args>
struct has_construct_helper : std::false_type{};

template <typename Alloc, typename T, typename... Args>
struct has_construct_helper<Alloc, T, 
    void_t<decltype(declval<Alloc &>().construct
                   (declval<T *>(), declval<Args>()...))>,
    Args...> : std::true_type
{};

template <typename Alloc, typename T, typename... Args>
using has_construct = typename has_construct_helper<Alloc, T, void, Args...>::type;

template <typename Alloc, typename T, typename... Args>
void doConstruct(std::true_type, Alloc &a, T *p, Args&&... args)
{
    a.construct(p, forward<Args>(args)...);
}

template <typename Alloc, typename T, typename... Args>
void doConstruct(std::false_type, Alloc &, T *p, Args&&... args)
{
    ::new (static_cast<void *>(p)) T(forward<Args>(args)...);
}


template <typename Alloc>
class allocator_traits {
public:
//...
    template <typename T, typename... Args> static
    void construct(Alloc &a, T *p, Args&&... args)
    {
        doConstruct(has_construct<Alloc,T,Args...>{}, a, p, forward<Args>(args)...);
    }
//...
}; 

Live Demo