6

The silly question. Could anyone please explain to me how this code works? (from here https://alexpolt.github.io/type-loophole.html)

#include <string>
#include <type_traits>

template<int N> struct tag{};

template<typename T, int N>
struct loophole_t {
  friend auto loophole(tag<N>) { return T{}; };
};

auto loophole(tag<0>);

int main() {

sizeof( loophole_t<std::string, 0> );
    
static_assert(std::is_same< std::string, decltype( loophole(tag<0>{}) ) >::value);

}

It looks like sizeof( loophole_t<std::string, 0> ); affects compiler global state. I mean if we remove this line static_asserts fails. Is at allowed for C++?

UPDATE: Just realized it depends on compiler or even compiler version. Works pretty well with any GCC >=8 (probably with older versions as well). Does not compile with clang >= 10, but works fine with clang 7.0

So I'd say my real question whether it is a compiler bug or a standard behavior?

Dmitry
  • 1,065
  • 7
  • 15

1 Answers1

4

It causes instantiation of the template specialisation loophole_t<std::string, 0>.

As a class template with a friend function (and remember, friends aren't members), that also brings the function into scope in the global namespace.

That function could be spelt std::string loophole(tag<0> unusedParam);.

It's not directly used for anything other than a throw-away sizeof after that, except to "retrieve" its return type with decltype and compare it to std::string in a static assertion (which is expected to pass, by way of demonstration).

The author has "stored" std::string in the expression tag<0>. Sort of.

If you wrote more of them:

sizeof( loophole_t<std::string, 0> );
sizeof( loophole_t<int, 1> );
sizeof( loophole_t<char, 2> );

… you'd end up with a whole bunch of functions in scope, which could be spelt:

std::string loophole(tag<0> unusedParam);
int loophole(tag<1> unusedParam);
char loophole(tag<2> unusedParam);

… and now you can see that the function declarations "store" a type for each tag. We can "access" the types using the tags:

decltype(loophole(tag<0>{})) thisIsAString = "lol";
decltype(loophole(tag<1>{})) thisIsAnInt = 42;
decltype(loophole(tag<2>{})) thisIsAChar = '!';

Of what actual benefit this is, I do not know. But if you're desperate for it, you could just:

using MyTypes = std::tuple<std::string, int, char>;

… then use somewhat more idiomatic means to extract type N.

Asteroids With Wings
  • 17,071
  • 2
  • 21
  • 35
  • Re: "what actual benefit": reflection. For ex. to get a type of a member of a structure. Otherwise, how you'd do this? – pmor Feb 18 '22 at 17:18