即使此转换不合法,为什么std :: is_convertible_v返回true?

时间:2019-08-18 21:19:58

标签: c++ templates implicit-conversion typetraits

我正在研究一个C ++项目,该项目需要使用类型自省来确定模板是哪种对象类型。这样,我遇到了一个不寻常的问题。我已将根本问题隔离为std::is_convertible_v所说的和实际可转换的类型之间不匹配。这是一个演示问题的简化示例。

这是一个名为LookForNested的类型,它具有一个模板构造函数,该构造函数在模板参数类型的内部查找名为NestedType的嵌套类型。

struct LookForNested {
  /* Look inside the argument type for a nested type. This won't
   * compile if you invoke the constructor on a type that doesn't have
   * a nested type with name NestedType.
   */
  template <typename T> LookForNested(const T&) {
    typename T::NestedType x;
  }
};

令人惊讶的是,即使您无法使用整数初始化static_assert,此LookForNested也会失败:

static_assert(!std::is_convertible_v<int, LookForNested>, "Can't initialize with int");

documentation for std::is_convertible建议通过检查是否可以编写返回int且返回类型为LookForNested的函数来完成检查。只是为了确认,实际上,您无法执行此操作,我对该函数进行了编码。如预期的那样,它不会编译。

/* Attempt to initialize a LookForNested with an int; this fails. */
LookForNested returnAnInt() {
  return 137;
}

我现在很困惑,因为

  • std::is_convertible_v特性表明您确实可以使用LookForNested初始化int,但是
  • 实际上,您不能将int转换为LookForNested

(顺便说一下,我正在使用g ++(Ubuntu 7.4.0-1ubuntu1〜18.04.1)7.4.0。)

我的问题如下:

  1. 是什么原因导致这种差异?
  2. 如果我想测试int是否真的可以转换为LookForNested,可以代替std::is_convertible_v使用什么?

谢谢!

1 个答案:

答案 0 :(得分:1)

  1. 正如评论中的Kerrek SB和chris所述,std::is_convertible仅考虑构造函数的声明,而不考虑其定义-它不执行模板实例化,因此存在差异。

  2. 您仍然可以使用std::is_convertible,但是必须更改LookForNested的构造函数,使其在声明中显示其对T::NestedType的依赖性。

您可以检查它是否根本存在:

template <typename T, typename T::NestedType* = nullptr>
LookForNested(const T&);

或执行更详尽的检查,例如它是否默认可构造:

template <typename T, std::enable_if_t<std::is_default_constructible_v<typename T::NestedType>>* = nullptr>
LookForNested(const T&);

完整示例(https://gcc.godbolt.org/z/FT_eaX):

#include <type_traits>

struct LookForNested {
  /* Look inside the argument type for a nested type. This won't
   * compile if you invoke the constructor on a type that doesn't have
   * a nested type with name NestedType.
   */
  template <typename T, std::enable_if_t<std::is_default_constructible_v<typename T::NestedType>>* = nullptr>
  LookForNested(const T&) {
    typename T::NestedType x;
  }
};

struct Good {
    using NestedType = double;
};

struct Bad {};

static_assert(!std::is_convertible_v<int, LookForNested>, "Can't initialize with int");
static_assert(std::is_convertible_v<Good, LookForNested>, "Can initialize with struct which has NestedType");
static_assert(!std::is_convertible_v<Bad, LookForNested>, "Can't initialize with struct which does not have NestedType");