4

For C++ types, the <type_traits> header gives us many useful compile-time reflection capabilities. E.g. std::is_base_of<B, D>::value determines at compile-time whether B is a base class of D.

I wonder if it would be possible to detect namespace membership along similar lines? E.g. given a namespace N with a type T, is there a way to determine whether T is contained within N using a macro expression of the form IS_NAMESPACE_MEMBER_OF(T,N).

I'd prefer a compile-time answer through any sort of SFINAE / ADL type of trick. Or, if it isn't possible, some sort of reasoning why the Standard would not allow this.

A non-portable and run-time hack would be to regex typeid(T).name() for N, but this is rather tedious and not at compile-time.

EDIT1: as pointed out by K-ballo, a namespace cannot be used as a template parameter so a type-trait seems impossible.

EDIT2: here's the skeleton as hinted to by K-ballo: what nifty test can (or cannot?) be cooked up there?

#define IS_NAMESPACE_MEMBER_OF(T, N) \
                                     \
// global declaration                \
void test(T);                        \
                                     \
// namespace declaration             \
namespace N {                        \
    void test(T);                    \
}                                    \
                                     \
// some clever name lookup / sizeof / SFINAE test!     
TemplateRex
  • 69,038
  • 19
  • 164
  • 304
  • 1
    Say you have: `struct T {}; namespace N { struct T; }`, would you want the result to be true/false? What about this `namespace A { struct T {}; } namespace B { using ::A::T; }` is `T` part of `A` namespace? – David Rodríguez - dribeas May 20 '12 at 00:27
  • 1
    The problem with the macro approach in the edit (that is, creating functions) is that it would be of very limited use, as it could only be used at namespace level and *outside* of any other namespace... – David Rodríguez - dribeas May 20 '12 at 00:31
  • @DavidRodríguez-dribeas In regard to your first comment, I'd say: yes, `T` is part of `A`, and also `T` is part of `B`. Why would it not be possible for `T` to be a member of multiple (non-nested) namespaces? After all, `T` can also be a base or derived class of many other classes. I'm not after: determine the entire chain of scopes of `T`. Just the simple question: is `T`contained in `N` (either through direct definition or through `using` statements. – TemplateRex May 21 '12 at 20:36
  • @DavidRodríguez-dribeas Your second comment is a good point. But one could wrap the entire macro in another namespace to prevent polluting the global scope, right? – TemplateRex May 21 '12 at 20:38
  • By definition you cannot *wrap* a macro in a namespace. Macro expansion happens *before* the compiler has a chance at processing namespaces. As of the questions, it does matter since in the language a type is defined in exactly one namespace, even if you can bring it into scope for lookup purposes to other namespaces, types or functions. I don't really see how this could be used for anything at all, but these are some of the things to consider. – David Rodríguez - dribeas May 22 '12 at 03:22

2 Answers2

4

A namespace is not a valid template parameter, so it could never be a class trait. Perhaps you can do something obscure with macros though. You could maybe inject functions in the test namespace and use ADL together with a sizeof/decltype trick to see which overload gets picked.

K-ballo
  • 80,396
  • 20
  • 159
  • 169
  • Thanks for pointing out my silly mistake of letting a namespace being a template parameter: fixed in in the question. – TemplateRex May 18 '12 at 18:18
  • Your suggestion of injecting names seems interesting. It's somewhat similar to injecting function arguments into a sizeof + test function for type traits. Do you have any concrete code suggestions for me to try? – TemplateRex May 18 '12 at 18:22
  • 3
    Indeed, is something similar. However, ADL would only tell you if the namespace is part of the associated namespace set of the class and not whether the class belongs to such namespace. I believe testing for namespace membership is possible using some intricate properties about namespaces, however I don't think its actually useful to give it more thought – K-ballo May 18 '12 at 18:26
  • Thanks again for the further hint on assocatiated namespace set. In any case, as the tag `language-lawyer` indicates, I do find it useful to get an answer to! If you have any more ideas on how to combine the subtle properties of macros, name lookup and sizeof I'd be very interested (think bounty). – TemplateRex May 18 '12 at 20:11
  • Some time ago I found a curious property of namespaces and I got close to a solution to your question. Unfortunately I cannot remember what was based on, but it wasn't ADL. That what leads me to believe that its possible, I got an almost working solution to it. I know, not very helpful... – K-ballo May 18 '12 at 20:14
1

You can test whether the namespace is accessible (looked up by the compiler), via ADL, from the type.

Suppose that we want to check if type A comes from namespace foo, we can try to use a type that appears only in foo (e.g. a generic function foo::foo_inner_func(T&&)) via the use of A to see if we reach the namespace. If we do it in a SFINAE context then this can result with the answer we are looking for: whether namespace foo is accessible via A.

In many cases that would be the answer of whether the type belongs to this namespace, but in some cases it may identify a namespace as accessible by ADL even though the type doesn't come from this namespace. For example if A is from namespace foo and B which derives from A is from another namespace, B still "sees" foo via ADL. Also std::vector<A> "sees" foo via ADL (and also "sees" std via ADL).

The idea of using ADL was already presented here: Check if a type is from a particular namespace.

Here is the macro version that allows querying any type (almost) for any namespace (almost):

#define create_ns_checker(ns) \
namespace ns { \
    template <typename T> \
    constexpr std::true_type ns##FindmeNsADLHelper(T&&); \
} \
namespace ns##_type_traits { \
    class ns##SecondBestMatchType {}; \
    class ns##BestExactMatchType : public ns##SecondBestMatchType {}; \
    namespace helpers { \
        template <typename T> \
        auto TestNs(ns##_type_traits::ns##BestExactMatchType) \
               -> decltype(ns##FindmeNsADLHelper(std::declval<T>())); \
        template <typename T> \
        auto TestNs(ns##_type_traits::ns##SecondBestMatchType) \
               -> std::false_type; \
    } \
    template <typename T> \
    constexpr bool ns##IsFindmeNs() { \
        return decltype(helpers::TestNs<std::decay_t<T>> \
                           (ns##BestExactMatchType{}))::value; \
    } \
}

#define is_in_ns(Type, ns) \
(ns##_type_traits::ns##IsFindmeNs<Type>())

A small printing utility:

#define print_is_in_ns(Type, ns) \
[]() { \
    std::cout << #Type << " in " << #ns << ": "  \
              << is_in_ns(Type, ns) << std::endl; \
}()

Creating the checkers with the macro:

create_ns_checker(findme)
create_ns_checker(other)
create_ns_checker(std)

Checking it for the following types:

namespace other {
    struct B {};
}

struct C {};

namespace findme {
    struct A {};

    namespace inner {
        struct A {};
    }
    create_ns_checker(inner)
}

Testing in findme context:

namespace findme {
    void test() {
        using namespace other;
        // add the below in and the results change, as it should!
          // using inner::A;
        using std::string;
        std::cout << std::boolalpha;
        print_is_in_ns(int, std);          // false
        print_is_in_ns(string, std);       // true
        print_is_in_ns(A, findme);         // true
        print_is_in_ns(A, inner);          // false
        print_is_in_ns(inner::A, findme);  // false
        print_is_in_ns(inner::A, inner);   // true
        print_is_in_ns(B, findme);         // false
        print_is_in_ns(B, other);          // true
        print_is_in_ns(C, findme);         // false
    }
}

Testing in main:

int main() {
    using std::string;
    using findme::A;
    std::cout << std::boolalpha;
    print_is_in_ns(int, std);                 // false
    print_is_in_ns(string, std);              // true
    print_is_in_ns(string, findme);           // false
    print_is_in_ns(findme::A, findme);        // true
    print_is_in_ns(findme::inner::A, findme); // false
    print_is_in_ns(other::B, findme);         // false
    print_is_in_ns(other::B, other);          // true
    print_is_in_ns(C, findme);                // false
    print_is_in_ns(std::vector<A>, findme); // falsely says true :-(
    print_is_in_ns(std::vector<A>, std);      // true
    std::cout << "-----------------" << std::endl;
    findme::test();
}

Code: https://godbolt.org/z/8Ed89v

Amir Kirsh
  • 12,564
  • 41
  • 74