Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why do type traits not work with types in namespace scope?

Tags:

c++

I'm designing a type register feature for my C++ serializing library. But I encountered a strange problem about type traits.

I'm using Visual Studio 2017 with /std:c++latest.

#include <type_traits>

int reg(...);

template<class T>
constexpr bool is_known = !std::is_same_v<decltype(reg((T*)1)), int>;

//----- for type1 in global scope ------
struct type1 {};
void reg(type1 *);
static_assert(is_known<type1>);  // success


//----- for type2 in namespace scope ----
namespace ns { struct type2 { }; }
void reg(ns::type2 *);
static_assert(is_known<ns::type2>); // fail!!!!

Static assert succeeds for type1, which is in global scope, but fails for namespace scope type2.

Why is there a difference?

like image 695
shawn5013 Avatar asked Apr 27 '19 07:04

shawn5013


People also ask

What is the scope of a namespace?

A namespace is a declarative region that provides a scope to the identifiers (the names of types, functions, variables, etc) inside it. Namespaces are used to organize code into logical groups and to prevent name collisions that can occur especially when your code base includes multiple libraries.

How do type traits work C++?

The type-traits library also contains a set of classes that perform a specific transformation on a type; for example, they can remove a top-level const or volatile qualifier from a type. Each class that performs a transformation defines a single typedef-member type that is the result of the transformation.

What is C++ trait class?

Traits are used throughout the C++ library. A trait is a class or class template that characterizes a type, possibly a template parameter. At first glance, it seems that traits obscure information, hiding types and other declarations in a morass of templates.


2 Answers

There are two sets of places examined when the lookup of reg((T*)) is done to find which reg is being referred to. The first is where the template is declared (where int reg(...) is visible), the second is ADL at the point where the template is first instantiated with a new type.

ADL (argument dependent lookup) on ns::type2* does not examine the global namespace. It examines namespaces associated with that type, namely ns in this case. ADL does not examine namespaces "surrounding" or "above" associated namespaces.

ADL for ::type1 does examine the global namespace.

Templates are not macros. They don't act as if you copy-pasted the generated code at the point you instantiated it. MSVC used to treat templates more like macros, but they have increasingly come into compliance with the standard. The name they gave to their compliance efforts is "two phase name lookup" if you want to track why it broke in a specific version.

The fix is to move reg into the namespace of ns::type2, or otherwise ensure that the namespace you define reg in is associated with the argument to reg (like use tag templates instead of pointers), or define reg before you define its use in decltype. Or something fancier; without underlying problem description I cannot guess.

like image 170
Yakk - Adam Nevraumont Avatar answered Sep 29 '22 12:09

Yakk - Adam Nevraumont


TLDR The mechanism is known as the 2 phase lookup, and its rules are arcane. Rule of thumb is to always declare functions in the same namespace as the type it uses to avoid shenanigans.

2 phase lookup occurs when there is a dependent name, at which point the name lookup is deferred to the point of instantiation. If the name is unqualified, the result of the lookup is the union of unqualified lookup at the point of definition and argument dependent lookup at the point of instantiation.

Now what the hell does that even mean?

Dependent name

A name (eg a function name) is dependent if its meaning depends on a template parameter. In your case, reg depends on T because the argument type T* depends on T.

Point of instantiation

Template aliases aren't types, they represent an entire family of types. The type is said to be instantiated from the template when you give it a parameter. The point of instantiation is the place in the program where the template alias is first used with an actual parameter.

Unqualified name

A name is said to be unqualified if there is no scope resolution operator before it, eg reg is unqualified.

Unqualified lookup

Whenever a name appears in the program, its declaration has to be found, this is called name lookup. Unqualified lookup looks up the name from the scope where the name appears and searches outwards sequentially.

Argument dependent lookup

Also known as ADL, which is another lookup rule, it applies when the function name being looked up is unqualified and one of a function's arguments is a user defined type. It finds the name in the associated namespaces of the type. The associated namespaces includes the namespace where the type is defined, among many others.

In conclusion, since is_known is defined before the following overloads of reg, unqualified lookup may only find reg(...). Since reg(ns::type2*) isn't within the associated namespace of ns::type2, it isn't found by ADL either.

like image 39
Passer By Avatar answered Sep 29 '22 12:09

Passer By