43

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.

Rumburak
  • 3,416
  • 16
  • 27
  • This solution is quite intrusive. To put this into a library, I guess I will make a macro for defining namespaces, which sets `adl_is_member_of_sample()` in place after `namespace xxx {`. – Lingxi Jan 24 '16 at 11:39
  • By the way, is there a name for the technique you have used with the `is_member_of_sample` template? – Lingxi Jan 24 '16 at 11:51
  • @YamMarcovic No. I mean the specialization part. I'm considering using the same technique to fight this [issue](https://groups.google.com/a/isocpp.org/d/msg/std-proposals/q3WCpLp1xmQ/nyekTQLVGAAJ). – Lingxi Jan 24 '16 at 11:54
  • @Lingxi Do you mean ADL? Or, could you pinpoint exactly which part you're referring? – Yam Marcovic Jan 24 '16 at 12:01
  • Do you know the specific type that you want to check? You could use its fully qualified name (like `::foo::bar::baz`) with `std::is_same` I think. – alcedine Feb 05 '16 at 09:49
  • @alcedine No, I want to have a generic template like `is_member_of_sample` that can test any type T. – Rumburak Feb 05 '16 at 13:06
  • 3
    Impressive technique, I wouldn't have thought this possible at all. Sadly, your technique has a few problems beyond the one you mentioned. First and worst, a using namespace on the target namespace breaks it. The other problem is that ADL doesn't just look in the argument's direct namespace, but all associated namespaces, which includes namespaces of base classes (IIRC) and template arguments. So this would misidentify `std::vector` as a member of the namespace. – Sebastian Redl Feb 11 '16 at 07:43
  • At runtime, you could inject some code into a file, and see if it compiles :) – Trevor Hickey Feb 11 '16 at 07:54
  • @SebastianRedl Thanks for the enlightenment! I was aware of base classes, but I had no idea that ADL looks at template arguments as well. That poses an interesting meta question: What does "X is a member of namespace Y" mean? For instance, is `std::vector` a member of both `std` and `sample`? – Rumburak Feb 11 '16 at 11:28
  • Yes, that's an interesting question. The other interesting question is why you even need the test you're writing here. – Sebastian Redl Feb 11 '16 at 11:52
  • @SebastianRedl Fair enough. I added a rationale. – Rumburak Feb 11 '16 at 13:19
  • BTW , in such cases SFINAE may be helpful – Cherkesgiller Nov 03 '16 at 09:45
  • Sadly because nothing currently allows namespaces only in template arguments I don't think it gets any cleaner than the solution you already have. – Hatted Rooster Nov 03 '16 at 10:13
  • In my opinion, you should rather change the `using is_numeric` from `void` to a type that is clearly indicating your usage scenario. `namespace Sample{ class numeric_marker; }` and `class foo { using is_numeric = ::Sample::numeric_marker; };` would be a base to build on, requiring type equality between `::Sample::numeric_marker` and `foo::is_numeric`. But I'm kinda out of current development regarding C++11 onwards, so maybe its harder than I imagine to have the actual type comparison at compile time. – grek40 Nov 11 '16 at 12:04
  • Similar: http://stackoverflow.com/q/10657711/819272 – TemplateRex Feb 05 '17 at 08:26

3 Answers3

8

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

Stacker
  • 1,080
  • 14
  • 20
  • This looks quite promising. I'll need some time to think about it. – Rumburak Nov 11 '16 at 06:09
  • 1
    Awesome! This is beyond what I had hoped for. If other compilers should be needed one day, the path should be very clear now. Very cool idea indeed. – Rumburak Nov 12 '16 at 20:16
-1

Unfortunately this technique works only for non-template types. For template types, ADL also checks the namespaces of the template arguments. Then it collects a list of class or function templates (depending on the context ADL is invoked from) and selects the best candidate.

A better solution would be to add an explicit check to the types whose membership in a namespace you want to check. For example, you could derive all types from a certain class or add a special member to each of them. This would be much clearer, easier to understand and maintain solution.

p12
  • 1,161
  • 8
  • 23
  • As discussed in the comments: An interesting question is what does "X is a member of namespace Y" mean? Thus ADL may or may not be a problem. Inheritance or tagging on the other hand is not an answer to the question. It would be an answer to "How can I tag a bunch of templates or classes"? And it would not work for any class except those that I write. – Rumburak Mar 04 '16 at 17:01
-3
std::cout << "I am " << __PRETTY_FUNCTION__ << " function." << std::endl; 

should print

namespace::class::function.
463035818_is_not_an_ai
  • 109,796
  • 11
  • 89
  • 185
Deividas
  • 23
  • 3