在存在泛型构造函数的情况下,对三元运算符的SFINAE失败

SFINAE on ternary operator fails in the presence of generic constructor

本文关键字:三元 运算符 失败 SFINAE 泛型 存在 构造函数 情况下      更新时间:2023-10-16

我有以下代码,参见另一个实例:

template <typename A, typename B>
using ternary = decltype(true ? std::declval <A>() : std::declval <B>());
template <template <typename, typename> class F>
struct test
{
    template <typename A, typename B, typename T = F <A, B> >
    static std::true_type call(int);
    template <typename A, typename B>
    static std::false_type call(...);
};
template <template <typename, typename> class F, typename A, typename B>
using sfinae = decltype(test <F>::template call <A, B>(0));
template <typename T> struct X { };
template <typename T> struct Y
{
    template <typename A>
    Y(A&& a) { }
};
int main ()
{
    using A = X <int>;
    using B = X <double>;
    using C = Y <int>;
    using D = Y <double>;
    sfinae <ternary, A, B>{};  // OK
    sfinae <ternary, C, D>{};  // GCC error:
           // operands to ?: have different types ‘Y<int>’ and ‘Y<double>’
}

这是对实际代码极度简化的结果,所以不要问它是否有用。粗略地说,sfinae <ternary, A, B>做了一个非常标准的SFINAE测试,测试是否可以将三元运算符?:应用于A, B类型的两个参数。

Clang编译得很好。GCC可以使用类模板X进行第一次调用,但在使用Y进行第二次调用时出现错误。该错误表明SFINAE失败,编译器意外地尝试应用三元操作符,这是不应该的。SFINAE应该永远不会失败(导致编译器错误):它应该总是编译,返回true_typefalse_type(在这个例子中,它应该总是返回false_type)。

类模板XY之间的唯一区别是Y有构造函数

template <typename A>
Y(A&& a) { }

在我的实际代码中,有更多的构造函数,并且都配备了enable_if以允许消歧。为了更现实一点,Y应该是

template <typename T> struct Y
{
    using type = T;
    template <
       typename A,
       typename = typename std::enable_if <
          std::is_constructible<T, typename A::type>{}
       >::type
    >
    Y(A&& a) : /*...*/ { }
};

允许其他具有不同TY构造,但对于错误没有任何改变。通常情况下,三元运算符应该要么由于不同的类型而失败,要么由于泛型构造函数而不明确(情况是这样吗?),但无论哪种方式,SFINAE都应该报告false_type

这个构造函数如何使SFINAE失败?这里的GCC兼容吗?有解决方案吗?


编辑

如果我手动实例化

decltype(true ? std::declval <C>() : std::declval <D>());

main()中,clang说

error: conditional expression is ambiguous; 'Y<int>' can be converted to 'Y<double>'
       and vice versa

而对于A B它显示的是

incompatible operand types ('X<int>' and 'X<double>')

和GCC在这两种情况下都坚持相同的错误。我不知道这是否有帮助。

这听起来很像一个GCC bug。使用最新版本的GCC 4.9会产生以下错误:

sftern.cpp: In instantiation of ‘struct test<ternary>’:
sftern.cpp:17:57:   required by substitution of ‘template<template<class ...> class F, class ... A> using sfinae = decltype (test:: call<A ...>(0)) [with F = ternary; A = {X<int>, X<double>}]’
sftern.cpp:33:26:   required from here
sftern.cpp:10:27: error: pack expansion argument for non-pack parameter ‘A’ of alias template ‘template<class A, class B> using ternary = decltype ((true ?  declval<A>() : declval<B>()))’
     static std::true_type call(int);
                           ^
sftern.cpp:3:11: note: declared here
 template <typename A, typename B>
           ^

听起来你描述的bug已经被修复了,但是它仍然会出错。(只要道具数量正确,将一个包扩展为一个非包是没有问题的。)

我稍微改变了一下这个例子,我开始在clang中得到错误:

template <typename A, typename B>
struct ternary1
{
    typedef decltype(true ? std::declval <A>() : std::declval <B>()) type;
};
template <template <typename, typename> class F>
struct test1
{
    template <typename A, typename B, typename T = typename F <A, B>::type >
    static std::true_type call(int);
    template <typename A, typename B>
    static std::false_type call(...);
};
template <template <typename, typename> class F, typename A, typename B>
using sfinae1 = decltype(test1 <F>::template call <A, B>(0));
template <typename T> struct X { };
template <typename T> struct Y
{
    template <typename A>
    Y(A&& a) { }
};
int main ()
{
    using A = X <int>;
    using B = X <double>;
    using C = Y <int>;
    using D = Y <double>;
    sfinae <ternary, A, B>{}; //clang: Incompatible operand types ('X<int>' and 'X<double>')
    sfinae <ternary, C, D>{}; //clang: Conditional expression is ambiguous; 'Y<int>' can be converted to 'Y<double>' and vice versa
}

ternary相当于typename ternary1::type或我错过了什么?