三元运算符上的SFINAE在通用构造函数存在的情况下失败

时间:2014-03-26 01:21:02

标签: c++ gcc ternary-operator sfinae typetraits

我有以下代码,另请参阅live example

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>对是否可以将三元运算符?:应用于AB类型的两个参数进行了非常标准的SFINAE测试。

Clang编译好。使用类模板X进行第一次调用时,GCC正常,但使用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) : /*...*/ { }
};

允许其他Y具有不同T的构建,但不会因错误而改变。通常,三元运算符应该由于不同的类型而失败,或者由于通用构造函数而导致模糊(这种情况是这样的?)但是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

而对于AB表示

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

并且GCC坚持在两种情况下都出现与上述相同的错误。我不知道这是否有帮助。

2 个答案:

答案 0 :(得分:1)

这听起来很像GCC的错误。使用相当新的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>
           ^

听起来你描述的错误已被修复,但它仍然被绊倒了。 (只要项目数量正确,将包装扩展为非包装就没问题了。)

答案 1 :(得分:0)

我稍微改写了这个例子,我开始在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还是我错过了什么?