Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Check if a type is from a particular namespace

Tags:

c++

c++11

I would like to check if a type is from a particular namespace. Here is what I came up with:

#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(){}

This works fine as long as no one adds adl_is_member_of_sample to their own namespace (or even the global namespace). And of course, I would have to create such a construct for each namespace I want to test for.

Is there a better way to check at compile time if a type is from a particular namespace?


Rationale or "Why would I want that":

In an EDSL, I am checking type traits at compile time to see whether certain expressions are valid or not. Some of those type traits are fairly simple: If a class has a using is_numeric = void, then I treat it as a numeric expression. Works fine.

is_numeric is pretty generic though. Others might use it as well. I therefore thought about backing the trait with a check that the type is from the expected namespace.

like image 242
Rumburak Avatar asked Jan 24 '16 10:01

Rumburak


1 Answers

There is a (compiler specific) way to test whether a type is in a certain namespace, but I'll let you decide whether it is better than yours or not:

#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;
}

I tested this for MSVC2015 and some random online Clang compiler and GCC 6.1.0.

Thoughts:

  • The test accepts classes and structs from namespace sample and any sub-namespace.
  • It doesn't suffer the drawbacks of your solution
  • You might want to build in std::decay_t to remove CV qualifiers.
  • Obviously the code requires >=C++14 Edit: Not any more, C++11 is enough
  • Nobody likes macros Edit: Removed most macros
  • The code isn't very portable and most likely needs additional branches for certain compilers and compiler versions. It is up to your requirements if the solution is acceptable

Edit: Refactored code to make it more clear and added GCC support. Also, the namespace to test for can now be passed as a parameter

like image 178
Stacker Avatar answered Nov 02 '22 15:11

Stacker