【问题标题】:Some magic with SFINAE and CRTP using clang++ and g++使用 clang++ 和 g++ 的 SFINAE 和 CRTP 的一些魔法
【发布时间】:2018-02-06 14:50:20
【问题描述】:

代码

以下代码给出不同的输出,带有和不带有* 注释的行:

#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
    

问题

  • 编译器的这种行为是否符合标准?如果是,其背后的理由是什么?
  • 当用* 标记的行被注释时(输出为@ 987654336@ 带有-std=c++14 编译器标志和true 带有-std=c++17 一个)?

【问题讨论】:

  • 我认为is_complete 的格式不正确......它的值可能取决于它的实例化位置。
  • @Jarod42 但是magic 类型别名会如何影响这一点(更改is_complete SFINAE 类的实例化点)?
  • @constructor 我猜 using 声明会立即强制评估右侧。如果没有该声明,value 仅在访问时评估,当您访问 Foo::value 时。
  • 一旦程序格式不正确(无需诊断),它类似于 UB,任何事情都可能发生。
  • From dependent_name "如果非依赖名称的含义在定义上下文和模板特化的实例化点之间发生变化,则程序格式错误,不需要诊断。这在以下情况下是可能的:在非依赖名称中使用的类型在定义时不完整,但在实例化时完整”magic 似乎就是这种情况。

标签: c++ templates sfinae crtp incomplete-type


【解决方案1】:

使用 CRTP 时遇到的一个常见问题是基类实例化时,派生类不完整。这意味着您不能在派生类中使用成员 typedef。

仔细想想,这是有道理的:模板类实际上是一种基于给定模板类型生成新类类型的方法,所以直到编译器到达结束 }(在近似意义上),基类没有完全定义。如果基类没有完全定义,那么派生类显然也不能。

因为基类和派生类都是空的(在第一个示例中),编译器认为它们是完整的。我会说这是不正确的,但我不是一个期望,也不能确定。不过,这里的技巧是您在定义基类时实例化了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; 的简单解决方案将不起作用,因为派生在尝试访问typedef 的时基时不会完成。

【讨论】:

    【解决方案2】:

    把我的评论变成答案:

    我认为is_complete 使代码格式错误(无需诊断)...
    它的值可能取决于它在哪里实例化并破坏 ODR。

    class A; // A not complete yet
    static_assert(!is_complete<A>::value);
    class A{}; // Now it is
    static_assert(is_complete<A>::value);
    

    来自dependent_name

    如果非依赖名称的含义在定义上下文和模板特化的实例化点之间发生变化,则程序格式错误,无需诊断。这在以下情况下是可能的:

    • 在非依赖名称中使用的类型在定义时不完整,但在实例化时完整。

    magic 似乎就是这种情况。

    即使magic 被注释,下面的代码也应该是错误的:

    static constexpr bool value = is_complete<Derived>{};
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2018-07-21
      • 1970-01-01
      • 2014-03-11
      • 2012-12-22
      • 2013-03-30
      • 1970-01-01
      • 2012-07-25
      • 1970-01-01
      相关资源
      最近更新 更多