代码
下面的代码给出了不同的输出,带有和不带有 *
注释的行:
#include <iostream>
#include <type_traits>
template <bool>
using bool_void_t = void;
template <typename, typename = void>
struct is_complete : std::false_type
{
};
template <typename T>
struct is_complete<T, bool_void_t<sizeof(T) == sizeof(T)>> : std::true_type
{
};
template <typename Derived>
struct Base
{
static constexpr bool value = is_complete<Derived>{};
// using magic = bool_void_t<value>; // *
};
struct Foo : Base<Foo>
{
};
int main()
{
std::cout << std::boolalpha << Foo::value << std::endl;
}
输出
编译器及其标志
在这两种情况下,clang++ 5.0.0 都用作编译器,编译器标志为 -std=c++17 -Wall -Wextra -Werror -pedantic-errors
.
更高级的研究
clang++ 5.0.0(
-Wall -Wextra -Werror -pedantic-errors
)-std=c++14 -std=c++17 * is commented false true * is not commented false false
g++ 7.2.1(
-Wall -Wextra -Werror -pedantic-errors
)-std=c++14 -std=c++17 * is commented true true * is not commented false false
问题
- 编译器的这种行为是否符合标准?如果是,其背后的理由是什么?
- 当用
*
标记的行被注释时,C++14 和 C++17 之间的哪些差异可能导致使用 clang++ 编译器观察到的行为不同(使用-std=c++14
编译器标志时输出为false
,使用-std=c++ 时输出为
一)?true
17
最佳答案
使用CRTP时常遇到的一个问题是在实例化基类时,派生类不完整。这意味着您不能在派生类中使用成员类型定义等等。
当您考虑时,这是有道理的:模板类实际上是一种根据给定模板类型生成新类类型的方法,因此直到编译器到达结束 }
(在一个近似意义),基类没有完全定义。如果基类没有完全定义,那么派生类显然也不能。
因为基类和派生类都是空的(在第一个例子中),编译器认为它们是完整的。我会说这是不正确的,但我不是期望也不能确定。不过,这里的技巧是您在定义基类时实例化 is_complete
的值。完全定义派生类后,它就完成了。
此外,对于这很重要的示例,请考虑这样的事情:
template <typename>
class crtp_traits;
class derived;
template <>
class crtp_traits<derived>
{
public:
using return_type = int;
};
template <typename T>
class base
{
public:
auto get_value() const -> typename crtp_traits<T>::return_type
{
return static_cast<T const*>(this)->do_get_value();
}
};
class derived : public base<derived>
{
public:
auto do_get_value() const -> int
{
return 0;
}
};
给 derived
成员 typedef using return_type = int;
的简单解决方案将不起作用,因为 derived 不会在时基尝试访问 typedef 时完成.
关于c++ - 使用 clang++ 和 g++ 的 SFINAE 和 CRTP 的一些魔法,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48645716/