【问题标题】:Check if a type is from a particular namespace检查类型是否来自特定命名空间
【发布时间】:2016-01-24 10:50:46
【问题描述】:

我想检查一个类型是否来自特定的命名空间。这是我想出的:

#include <utility>

namespace helper
{
  template <typename T, typename = void>
  struct is_member_of_sample : std::false_type
  {
  };

  template <typename T>
  struct is_member_of_sample<
      T,
      decltype(adl_is_member_of_sample(std::declval<T>()))> : std::true_type
  {
  };
}

namespace sample
{
  template <typename T>
  auto adl_is_member_of_sample(T && ) -> void;
}

// -- Test it

namespace sample
{
  struct X;
}

struct Y;

static_assert(helper::is_member_of_sample<sample::X>::value, "");
static_assert(not helper::is_member_of_sample<Y>::value, "");

int main(){}

只要没有人将adl_is_member_of_sample 添加到他们自己的命名空间(甚至是全局命名空间),这就可以正常工作。当然,我必须为要测试的每个命名空间创建这样的构造。

有没有更好的方法在编译时检查一个类型是否来自特定的命名空间?


基本原理或“我为什么要这样做”:

在 EDSL 中,我在编译时检查类型特征以查看某些表达式是否有效。其中一些类型特征相当简单:如果一个类有一个using is_numeric = void,那么我将它视为一个数字表达式。工作正常。

is_numeric 虽然非常通用。其他人也可能会使用它。因此,我考虑通过检查类型是否来自预期的命名空间来支持 trait。

【问题讨论】:

  • 这个解决方案非常具有侵入性。要将其放入库中,我想我将创建一个用于定义命名空间的宏,它将 adl_is_member_of_sample() 设置在 namespace xxx { 之后。
  • 顺便问一下,您在is_member_of_sample 模板中使用的技术有名称吗?
  • @YamMarcovic 不。我的意思是专业化部分。我正在考虑使用相同的技术来解决这个问题issue
  • @Lingxi 你是说ADL吗?或者,您能否准确指出您指的是哪一部分?
  • 令人印象深刻的技术,我根本不会想到这可能。可悲的是,除了您提到的问题之外,您的技术还有一些问题。首先也是最糟糕的,目标命名空间上的 using 命名空间会破坏它。另一个问题是 ADL 不仅查看参数的直接命名空间,还查看所有关联的命名空间,其中包括基类 (IIRC) 和模板参数的命名空间。所以这会将std::vector&lt;sample::X&gt; 误认为是命名空间的成员。

标签: c++ c++11


【解决方案1】:

有一种(编译器特定的)方法可以测试一个类型是否在某个命名空间中,但我会让你决定它是否比你的更好:

#include <utility>
#include <type_traits>

namespace helper
{
class ctstring
{
public:
  constexpr ctstring(const char* string) : _string(string)
  {
  }

  constexpr const char* c_str() const
  {
    return _string;
  }

  constexpr bool begins_with(const ctstring other) const
  {
    return !*other.c_str() ||
           (*_string && *_string == *other.c_str() &&
            ctstring(_string + 1).begins_with(other.c_str() + 1));
  }

private:
  const char* _string;
};

template <typename T>
constexpr bool is_type_in_namespace(const ctstring name)
{
#if defined(_MSC_VER)
#define PRETTY_FUNCTION_OFFSET_1 \
  (sizeof("void __cdecl helper::is_type_in_namespace<struct ") - 1)
#define PRETTY_FUNCTION_OFFSET_2 \
  (sizeof("void __cdecl helper::is_type_in_namespace<class ") - 1)

  return ctstring(__FUNCSIG__ + PRETTY_FUNCTION_OFFSET_1).begins_with(name) ||
         ctstring(__FUNCSIG__ + PRETTY_FUNCTION_OFFSET_2).begins_with(name);

#undef PRETTY_FUNCTION_OFFSET_1
#undef PRETTY_FUNCTION_OFFSET_2
#elif defined(__clang__)
  return ctstring(__PRETTY_FUNCTION__ +
                  (sizeof("bool helper::is_type_in_namespace(const "
                          "helper::ctstring) [T = ") -
                   1))
    .begins_with(name);
#elif defined(__GNUC__)
  return ctstring(__PRETTY_FUNCTION__ +
                  (sizeof("constexpr bool "
                          "helper::is_type_in_namespace(helper::ctstring) "
                          "[with T = ") -
                   1))
    .begins_with(name);
#else
#error "Your compiler is not supported, yet."
#endif
}
}

// -- Test it

namespace sample
{
struct True_X;

class True_Y;

template <typename>
class True_T;

template <typename A>
using True_U = True_T<A>;
}

struct False_X;

class False_Y;

template <typename>
class False_T;

template <typename A>
using False_U = False_T<A>;

void test1()
{
  static_assert(helper::is_type_in_namespace<sample::True_X>("sample::"), "1");
  static_assert(helper::is_type_in_namespace<sample::True_Y>("sample::"), "2");
  static_assert(helper::is_type_in_namespace<sample::True_T<int>>("sample::"), "3");
  static_assert(helper::is_type_in_namespace<sample::True_U<int>>("sample::"), "4");
  static_assert(!helper::is_type_in_namespace<False_X>("sample::"), "5");
  static_assert(!helper::is_type_in_namespace<False_Y>("sample::"), "6");
  static_assert(!helper::is_type_in_namespace<False_T<int>>("sample::"), "7");
  static_assert(!helper::is_type_in_namespace<False_U<int>>("sample::"), "8");
}

namespace sample
{
void test2()
{
  static_assert(helper::is_type_in_namespace<True_X>("sample::"), "1");
  static_assert(helper::is_type_in_namespace<True_Y>("sample::"), "2");
  static_assert(helper::is_type_in_namespace<True_T<int>>("sample::"), "3");
  static_assert(helper::is_type_in_namespace<True_U<int>>("sample::"), "4");
  static_assert(!helper::is_type_in_namespace<::False_X>("sample::"), "5");
  static_assert(!helper::is_type_in_namespace<::False_Y>("sample::"), "6");
  static_assert(!helper::is_type_in_namespace<::False_T<int>>("sample::"), "7");
  static_assert(!helper::is_type_in_namespace<::False_U<int>>("sample::"), "8");
}

namespace inner
{
void test3()
{
  static_assert(helper::is_type_in_namespace<::sample::True_X>("sample::"), "1");
  static_assert(helper::is_type_in_namespace<::sample::True_Y>("sample::"), "2");
  static_assert(helper::is_type_in_namespace<::sample::True_T<int>>("sample::"), "3");
  static_assert(helper::is_type_in_namespace<::sample::True_U<int>>("sample::"), "4");
  static_assert(!helper::is_type_in_namespace<::False_X>("sample::"), "5");
  static_assert(!helper::is_type_in_namespace<::False_Y>("sample::"), "6");
  static_assert(!helper::is_type_in_namespace<::False_T<int>>("sample::"), "7");
  static_assert(!helper::is_type_in_namespace<::False_U<int>>("sample::"), "8");
}
}
}

void test4()
{
  using namespace sample;

  static_assert(helper::is_type_in_namespace<True_X>("sample::"), "1");
  static_assert(helper::is_type_in_namespace<True_Y>("sample::"), "2");
  static_assert(helper::is_type_in_namespace<True_T<int>>("sample::"), "3");
  static_assert(helper::is_type_in_namespace<True_U<int>>("sample::"), "4");
}

int main(int argc, char* argv[])
{
  test1();
  sample::test2();
  sample::inner::test3();
  test4();
  return 0;
}

我针对 MSVC2015 和一些随机的在线 Clang 编译器和 GCC 6.1.0 进行了测试。

想法:

  • 测试接受来自命名空间样本和任何子命名空间的类和结构。
  • 它没有您的解决方案的缺点
  • 您可能希望在 std::decay_t 中构建以删除 CV 限定符。
  • 显然代码需要 >=C++14 编辑:不再需要,C++11 就足够了
  • 没有人喜欢宏编辑:删除了大多数宏
  • 代码的可移植性不是很好,很可能需要针对某些编译器和编译器版本的附加分支。如果解决方案可以接受,这取决于您的要求

编辑: 重构代码使其更清晰,并添加了 GCC 支持。此外,现在可以将要测试的命名空间作为参数传递

【讨论】:

  • 这看起来很有希望。我需要一些时间考虑。
  • 太棒了!这超出了我的预期。如果有一天需要其他编译器,那么路径现在应该很清楚了。确实很酷的想法。
【解决方案2】:

不幸的是,这种技术只适用于非模板类型。对于模板类型,ADL 还检查模板参数的命名空间。然后它收集一个类或函数模板列表(取决于调用 ADL 的上下文)并选择最佳候选者。

更好的解决方案是向要检查其在命名空间中的成员资格的类型添加显式检查。例如,您可以从某个类派生所有类型或为每个类型添加一个特殊成员。这将更清晰,更易于理解和维护解决方案。

【讨论】:

  • 正如 cmets 中所讨论的:一个有趣的问题是“X 是命名空间 Y 的成员”是什么意思?因此,ADL 可能是也可能不是问题。另一方面,继承或标记不是问题的答案。这将是“如何标记一堆模板或类”的答案?除了我写的课程之外,它不适用于任何课程。
【解决方案3】:
std::cout << "I am " << __PRETTY_FUNCTION__ << " function." << std::endl; 

应该打印

namespace::class::function.

【讨论】:

  • 当然可以,但这与在编译时确定给定类型的命名空间有什么关系?
猜你喜欢
  • 2012-02-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-07-14
  • 2012-08-07
  • 1970-01-01
相关资源
最近更新 更多