C++
Raiting:
61

Why avoid friends, or how I lost all my strengths


Hello, Umumble.

A couple of days ago this tweet caught my eye:
C++ - Stateful TMP#cpp #cplusplus #Cpp20https://t.co/Q3sh3XtiHC pic.twitter.com/AkCRB2zvrT
- Kris Jusiak (@krisjusiak) October 21, 2019


In short: once again, in C ++, they found some kind of crap that appeared there on its own, emergent-convergently, like the slime from a short science fiction story I read as a child, which accidentally appeared in the city sewer and grew to a universal organic solvent.

Let's figure it out, since it won't be long (according to the text of the Standard, I jumped no more than a couple of hours). And fun, referencing the Standard is always fun.

Here's the whole code:

#include <cstdio>

class tag;

template<class>
struct type { friend constexpr auto get(type); };

template<class TKey, class TValue>
struct set { friend constexpr auto get(TKey) { return TValue{}; } };

void foo() { // never called
if constexpr(false) { // never true
if (false) { // never true
constexpr auto call = [](auto value) { std::printf("called %d", value); };
void(set<type<tag>, decltype(call)>{});
}
}
}

int main() {
get(type<tag>{})(42); // prints called 42
}
We will read it line by line.

class tag; Well, everything is clear.

template<class>
struct type { friend constexpr auto get(type); };
We declare the structure type. Note that it declares a function named get and some other parameter.

What happens if you instantiate ( 13.9.1 / 1 ) type<T> for some T? In the global namespace (but not in the global scope, but available for the argument-dependent lookup, this is important!) There will be a function declaration get (T) ( 9.8.1.2/3 , 13.9.1 / 4 ), albeit without definition ( 6.2 / 2.1 ).

template<class TKey, class TValue>
struct set { friend constexpr auto get(TKey) { return TValue{}; } };
We declare the structure set. It, in turn, defines a function named get and some parameter.

What happens if you instantiate set<K, V> for some K, V? The get (K) function will again enter the global namespace, but now together with the definition ( 6.2 / 2 ).

void foo() {
if constexpr(false) {
if (false) {
constexpr auto call = [](auto value) { std::printf("called %d", value); };
void(set<type<tag>, decltype(call)>{});
}
}
}
It is clear that if (false) has no effect on any template instantiations, as well as type casting, so let's simplify this snippet:

void foo() {
if constexpr(false) {
constexpr auto call = [](auto value) { std::printf("called %d", value); };
set<type<tag>, decltype(call)>{};
}
}
We all know that if constexpr is generally invented so that there is a simple possibility not to instantiate deliberately incorrect templates. What's going on here?

Let's take a closer look at the definition of if constexpr: 8.5.1 / 2 ... First of all, we are interested in this phrase:
If the value of the converted condition is false, the first substatement is a discarded statement
That is, our nonsense is ccall and set - discarded statement. Sounds promising so far.

Let's look at the following phrase:
During the instantiation of an enclosing templated entity, if the condition is not value-dependent after its instantiation, the discarded substatement (if any) is not instantiated.
This is the only mention of the discarded statement behavior, and the only mention of the case when it is not instantiated. False, of course, is not value-dependent, but there is one "but". This phrase speaks of "enclosing template entity", and we have an enclosing entity - function foo, which is in no way template. Accordingly, this phrase is inapplicable, and nothing is thrown anywhere, the body if constexpr is completely instantiated.

Then everything is clear. Body instantiated, type instantiated<tag> , the get (type<tag> ) in the global scope, visible through ADL if the get name argument is of type type<tag> (again 9.8.1.2/3). Further instantiated set <type<tag> , decltype (call)> which defines the function get (type<tag> ) declared at the time of instantiation<tag> ... The definition in this case returns a new value of the type decltype (call), and since call does not capture anything, and in C ++ 20 lambdas without a capture list can be constructed by default ( 7.5.5.1/13 ), then it will all work. In main we just call get (type<tag> {}), which finds the previously declared and defined get via the long-suffering ADL. It returns a lambda equivalent to call, which we immediately call with 42.

So it goes.

Note that the key point here is the interaction between the discarded statement and the enclosing template entity. Indeed, if we replace void foo () with template<typename> void foo () , and even if then explicitly call it

something like this #include<cstdio>

class tag;

template<class>
struct type { friend constexpr auto get(type); };

template<class TKey, class TValue>
struct set { friend constexpr auto get(TKey) { return TValue{}; } };

template<typename>
void foo() {
if constexpr(false) { // never true
if (false) { // never true
constexpr auto call = [](auto value) { std::printf("called %d", value); };
void(set<type<tag>, decltype(call)>{});
}
}
}

int main() {
foo<int>();
get(type<tag>{})(42); // prints called 42
}
then everything will break down, the pancake will be repaired, I don't know what it is and how to call it, I no longer have any expectations from the plus code will return to normal:

prog.cc:23:3: error: function 'get' with deduced return type cannot be used before it is defined
get(type<tag>{})(42); // prints called 42
^
prog.cc:6:37: note: 'get' declared here
struct type { friend constexpr auto get(type); };
^
In general, in C ++ a namespace scope is such a global state that can be changed (through the instantiation of template structures with friend functions), and which can be read (through SFINAE, detector idiom, and similar tricks). I wonder if this could be considered another Turing complete language within C ++?

In general, more and more often I find myself thinking that I don't even know what to say about C ++. On the one hand, the pros are my favorite imperative programming language, and I'm always in favor of doing something like that on templates. On the other hand, this is already some kind of madness when, in order to interpret a program, you need to remember whether there is a word template somewhere in some paragraph of the standard or not, because everything changes from this. It's not even crazy, it's just pure destruction, chaotic evil. On the third, many people consider wrapping on templates to be madness, so, probably, whoever put on the first robe is right.

However, in this particular case, nothing new. The technique of stateful metaprogramming was open back in the days of C ++ 14, and is quite implementable in C ++ 11, if not 03.

What's the solution? His not :
Defining a friend function in a template, then referencing that function later provides a means of capturing and retrieving metaprogramming state. This technique is arcane and should be made ill-formed.
Notes from the May, 2015 meeting:
CWG agreed that such techniques should be ill-formed, although the mechanism for prohibiting them is as yet undetermined.
Abusing the rules of the language in order to do such lewdness is bad, stupid.

image

Happy coding anyway!
Tags: C++, C++20
Papay 13 february 2021, 14:25
Vote for this post
Bring it to the Main Page
 

Comments

Leave a Reply

B
I
U
S
Help
Avaible tags
  • <b>...</b>highlighting important text on the page in bold
  • <i>..</i>highlighting important text on the page in italic
  • <u>...</u>allocated with tag <u> text shownas underlined
  • <s>...</s>allocated with tag <s> text shown as strikethrough
  • <sup>...</sup>, <sub>...</sub>text in the tag <sup> appears as a superscript, <sub> - subscript
  • <blockquote>...</blockquote>For  highlight citation, use the tag <blockquote>
  • <code lang="lang">...</code>highlighting the program code (supported by bash, cpp, cs, css, xml, html, java, javascript, lisp, lua, php, perl, python, ruby, sql, scala, text)
  • <a href="http://...">...</a>link, specify the desired Internet address in the href attribute
  • <img src="http://..." alt="text" />specify the full path of image in the src attribute