转换函数,std::is_base_of 和虚假的不完整类型:替换失败是一个错误

Conversion functions, std::is_base_of and spurious incomplete types: substitution failure IS an error

我正在尝试实现一个转换函数运算符,并使用 std::is_base_of 来限制适用范围,但我 运行 遇到了问题。

#include <type_traits>

class Spurious;

class MyClassBase {};

template< typename T >
class MyClass: public MyClassBase {
public:
    template< typename U, std::enable_if_t< std::is_base_of<MyClassBase, U>::value, bool> = true >
    operator U () const {
        return U{}; // Complex initialization omitted for brevity
    }
};

class MyBool: public MyClass< bool > {};
class MyInt: public MyClass< int > {};

int do_stuff( int i, Spurious const & obj);
int do_stuff( int i, MyBool const & obj) {
    return i;
}

int main() {
    MyInt const obj;
    return do_stuff( 3, obj );
}

Spurious 和相关函数定义的存在意味着我在 is_base_of 实施。

我明白为什么你不能 is_base_of 一个不完整的类型,但我不太明白为什么“替换失败不是错误”在这里不适用。我原以为在模板扩展期间尝试 is_base_of 一个不完整的类型会导致编译器中止转换尝试并继续下一个函数定义。这就是此处 is_base_of 语句的全部要点:忽略与模式不匹配的 类。

不过,我的主要问题不是“为什么”,而是“下一步是什么”。有什么解决方法吗?有没有什么方法可以忽略像 Spurious 这样的东西,同时又不需要在这个编译单元中完全定义它们?理想情况下,我只需修改转换运算符的定义(例如 SFINAE 模板参数)即可使其正常工作。

这不是 SFINAE,它肯定适用于不完整的类型:

template<class T,bool=false>
struct size : std::integral_constant<std::size_t,0> {};
template<class T>
struct size<T,!sizeof(T)> : std::integral_constant<std::size_t,sizeof(T)> {};
static_assert(size<char>::value==1);  // OK

struct A;
static_assert(size<A>::value==0);  // OK
struct A {};
static_assert(size<A>::value==0);  // OK!

这里的问题是 std::is_base_of 有“未定义的行为”(阅读:使用它会使程序 格式错误 ,不需要诊断)如果两种类型是非联合 class 类型,后者是不完整的。这不在 SFINAE 中,因为它不在 即时 上下文中,而且因为它是 NDR。

这些编译器选择拒绝代码,这有利于防止 意外,如上面的 // OK!

您可以使用直接 SFINAE 方法避免这个问题:

template< typename T >
class MyClass: public MyClassBase {
public:
    template< typename U, std::enable_if_t<
        (sizeof(U), std::is_base_of<MyClassBase, U>::value), bool> = true >
    operator U () const {
        return U{}; // Complex initialization omitted for brevity
    }
};

如果 U 不完整,则模板参数类型中存在直接替换错误,这 阻止 实例化 std::is_base_of 特化是 IFNDR。请注意,以这种方式在 SFINAE 中使用不完整类型仍然 危险 ,因为在相关类型完整的上下文中实例化的相同特化可能会产生不同的行为(即 还有 IFNDR,根据 [temp.point]/7).