﻿# C\+\+ programmer's guide to undefined behavior: part 5 of 11

Your attention is invited to the fifth part of an e\-book on undefined behavior\. This is not a textbook, as it's intended for those who are already familiar with C\+\+ programming\. It's a kind of C\+\+ programmer's guide to undefined behavior and to its most secret and exotic corners\. The book was written by Dmitry Sviridkin and edited by Andrey Karpov\.

![1160_book_pt_5/image1.png](https://import.viva64.com/docx/blog/1160_book_pt_5/image1.png)

## Broken syntax and standard library: Most Vexing Parse

In addition to undefined behavior, C\+\+ delights us with unexpected behavior that arises from the following fascinating language features\.

We can _declare_ user\-defined types and functions [wherever and however we want](https://godbolt.org/z/Wd9vPzrrr)\.

```cpp
template <class T>
struct STagged {};


using S1 = STagged<struct Tag1>; // the Tag1 struct forward
                                 // declaration
using S2 = STagged<struct Tag2*>; // the Tag2 struct forward
                                  // declaration

void fun(struct Tag3*); // the Tag3 struct forward declaration

void external_fun() {
  int internal_fun();   // the function forward declaration!
  internal_fun();
}

int internal_fun() {    // the forward-declared function definition
  std::cout << "hello internal\n";
  return 0;
}

int main() {
  external_fun();
}
```

However, we can't _define_ entities everywhere\. We can define types locally, within a function, but we can't define functions\.

```cpp
void fun() {
  struct LocalS {
    int x, y;
  };                         // OK

  void local_f() {
    std::cout << "local_f";
  }                          // Compilation Error
}
```

Everything's fine except for one thing: C\+\+ has constructors, which are called [similar](https://godbolt.org/z/bv18qxex6) to declaring a function\.

```cpp
struct Timer {
  int val;
  explicit Timer(int v = 0) : val(v) {}
};

struct Worker {
  int time_to_work;

  explicit Worker(Timer t) : time_to_work(t.val) {}

  friend std::ostream&
  operator << (std::ostream& os, const Worker& w) {
    return os << "Time to work=" << w.time_to_work;
  }
};

int main() {

  // THIS ISN'T A CONSTRUCTOR CALL!
  Worker w(Timer()); // A forward declaration of the function
                     // that returns Worker and takes the function
                     // that returns Timer and doesn't take anything!

  std::cout << w;    // The function name is implicitly converted to a
                     // pointer that is implicitly converted to bool.
                     // 1 (true) is displayed
}
```

Such a bug can be difficult to detect if the forward\-declared function is accidentally contextually converted to _bool_, or if the object being constructed can be called \(its _operator\(\)_ is overloaded\)\.

It may seem that the default constructor of the _Timer_ class is to blame\. However, it's C\+\+\. There, we can declare functions like this:

```cpp
void fun(int (val)); // Parentheses are allowed around the parameter name!
```

So, you can get a [nastier](https://godbolt.org/z/385v31xor) and harder to understand version of the error:

```cpp
int main() {
  const int time_to_work = 10;
  Worker w(Timer(time_to_work)); // Forward declaration of the function
                                 // that returns Worker
                                 // and takes the parameter of
                                 // the Timer type.
                                 // time_to_work is this parameter name!

  std::cout << w;                // Prints 1
}
```

GCC and Clang can warn you about such things\.

C\+\+11 and its later versions offer [uniform initialization](https://en.cppreference.com/w/cpp/language/initialization#Initializer_semantics) \(via _\{\}_\), which isn't quite universal and has its own issues\. C\+\+20 offers another universal initialization but via _\(\)_ again\.\.\.

We can avoid the issue by using the Almost Always Auto approach with the _auto w \= Worker\(Timer\(\)\)_ initialization\. Parentheses or curly braces aren't that important here \(well, they are but in a different context\)\.

Maybe someday declaring functions in the old C style will be banned in favor of _trailing return type_ \(_auto fun\(args\) \-\> ret_\)\. And it will be much harder \(but still [possible](https://www.youtube.com/watch?v=tsG95Y-C14k)\!\) to run into the discussed issue\.

1. Jonathan Boccara\. [The Most Vexing Parse: How to Spot It and Fix It Quickly](https://www.fluentcpp.com/2018/01/30/most-vexing-parse/)\. 
1. [Almost Always Auto style](http://cginternals.github.io/guidelines/articles/almost-always-auto/)\.
1. Wikipedia\. [Most vexing parse](https://en.wikipedia.org/wiki/Most_vexing_parse)\.
1. Sebastian Werhausen\. [Potential issue with C\+\+20's initialization change](https://gist.github.com/s9w/ad9b1dd1ea6fb17e956559c8b352e246)\.

## Broken syntax and standard library: non\-constant constants

C\+\+ has the _const_ keyword that enables you to mark values as immutable\. Also, C\+\+ has _const\_cast_ to ignore this _const_\. Sometimes you'll get away with it\. And sometimes there'll be undefined behavior, a segfault, and other exciting things\.

The difference between these "sometimes" lies in whether you're dealing with real constants, where any attempt to modify them leads to UB, or just references to constants referring to non\-constants\. Since the object is actually non\-constant, modifying it is no problem\.

For example, we can [leverage](https://godbolt.org/z/cjYqKfczM) this feature to avoid repeating the same code for _const_ and non\-_const_ qualified versions of class member functions:

```cpp
class MyMap {
public:
  // some member function with a lengthy implementation:
  const int& get_for_val_or_abs_val(int val) const {
    const auto it_val = m.find(val);
    if (it_val != m.end()) {
      return it_val->second;
    }
    const auto abs_val = std::abs(val);
    const auto it_abs = m.find(abs_val);
    if (it_abs != m.end()) {
      return it_abs->second;
    }
    throw std::runtime_error("no value");
  }

  int& get_for_val_or_abs_val(int val) {
    return const_cast<int&>( // We discard const from the result.
      // Since this is a non-constant member function,
      // we know that the result
      // isn't really a constant,
      // and there won't be any issues.
      std::as_const(*this) // We add const
                           // to call the const-qualified
                           // member function and avoid
                           // infinite recursion.
      .get_for_val_or_abs_val(val));
  }

  void set_val(int val, int x) {
    m[val] = x;
  }
private:
  std::map<int, int> m;
};
```

Avoid such code if possible\. You can see that it's so fragile that a forgotten or accidentally deleted _std::as\_const_ breaks it\. And without specific warnings configured, compilers [are slow](https://godbolt.org/z/Mn8GYqqe8) to inform about it\.

Instead of using _const\_cast_ and bringing even more instability to the C\+\+ world, we can eliminate code duplication by using a template method:

```cpp
class MyMap {
public:
  const int& get_for_val_or_abs_val(int val) const {
    return get_for_val_or_abs_val_impl(*this, val); // *this — const&
  }

  int& get_for_val_or_abs_val(int val) {
    return get_for_val_or_abs_val_impl(*this, val); // *this  — &
  }

  void set_val(int val, int x) {
    m[val] = x;
  }
private:
  template <class Self> static decltype(auto)
    get_for_val_or_abs_val_impl(Self& self, int val)
  {
    auto&& m = self.m;
    if (it_val != m.end()) {
      // Additional parentheses to display the value category
      return (it_val->second);
    }
    const auto abs_val = std::abs(val);
    const auto it_abs = m.find(abs_val);
    if (it_abs != m.end()) {
      return (it_abs->second);
    }
    throw std::runtime_error("no value");
  }

  std::map<int, int> m;
};
```

This option has its drawbacks, and it's even easier to break \(parentheses and _decltype_\)\. But having written it once, one can be sure that some strange magic will scare away those who want to fix this code\.

Of course, instead of _decltype\(auto\)_ we can write a bit more code explicitly specifying the types of return values\.

### Const and optimizations

Operations on immutable data are perfectly optimized, parallelized, and run well in general\.

However, all this fuss with removing and adding _const_ anywhere in the code eliminates this set of optimizations\. So, a repeated access by a constant reference to the same data member or member function doesn't need to be cached at all\.

**Note\.** It's worth mentioning that programmers have unrealistic expectations about the compiler optimizing code when they add more _const_\. Here's a good note on the topic: "[Why const Doesn't Make C Code Faster](https://theartofmachinery.com/2019/08/12/c_const_isnt_for_performance.html)"\.

For example, iterating through a vector can't be [optimized](https://godbolt.org/z/1fs4W3) in such a simple case:

```cpp
using predicate = bool (*) (int);

int count_if(const std::vector<int>& v, predicate p) {
  int res = 0;
  for (size_t i = 0; i < v.size(); ++i) // We can't save the v.size() value
  {                                     // once in the register.
    if (p(v[i])) // A specific 'p' can access 
    {            // the same 'v' via a reference to non-const.
      ++res;
    }
    // the size() member function has to be executed at each iteration!
  }
   return res;
}
```

The example that prohibits optimization may not be obvious, but it's simple:

```cpp
std::vector<int> global_v = {1};

bool pred(int x) {
  if (x == global_v.size()) {
    global_v.push_back(x);
    return true;
  } else {
    return false;
  }
}

int main() {
  return count_if(global_v, pred);
}
```

The code is terrible\. It shouldn't be anywhere\. And it would never pass code review\. However, in theory, one can write it that way, so the optimization isn't done\.

If we use a template parameter as a predicate type, we can create much more tricky examples without involving global variables\.

Given the limited possibilities for automatic optimization, devs usually rewrite such a loop \(doing exactly what the compiler was supposed to do\):

```cpp
int count_if(const std::vector<int>& v, predicate p) {
  int res = 0;

  // range-based-for doesn't access size()
  // but it reads begin/end iterators once and handles
  // them. 
  for (auto x : v) { 
    if (p(v[i])) {
      ++res;
    }
  }
  return res;
}
```

In such a case, when passing a "bad" predicate that changes the vector, we get undefined behavior\. But that's a story for another time\.\.\.

Here's a less trivial example of _const_ that doesn't contribute to optimization in any way:

```cpp
void find_last_zero_pos(const std::vector<int>& v,
                        const int* *pointer_to_last_zero) {
  *pointer_to_last_zero = 0;
  // Again, we can't save value of v.size() once
  for (size_t i = 0; i < v.size(); ++i) {
    if (v[i] == 0) {
      // Inside the vector, there are the 'int *' type data members,
      // begin and end.
      // What if 'pointer_to_last_zero' points to one of them!?
      *pointer_to_last_zero = (v.data() + i);
    }
    // recalculating size!
  }
}
```

If we follow the recommended C\+\+ coding practices, we can't construct an example that demonstrates the optimization inapplicability\. Encapsulation stops us\. We can't legitimately get to the private data members of the vector\.

However, nothing stops us from writing non\-normal code\! Let's use brute force:

```cpp
int main() {
  std::vector<int> a = {1,2,4,0};
  const int* &data_ptr =
    reinterpret_cast<const int* &>(a); // a reference to begin!
  find_last_zero_pos(a, &data_ptr);
}
```

Here's a paradox: the mere chance to write clearly incorrect code forbids the compiler to optimize the loop\! The whole concept of undefined behavior as a potential for optimization \(claiming that there's no such thing as incorrect code\) falls apart\.

Well, the example at least gives a measure of stability: the original loop with a counter and the rewritten one to be range\-based\-for end with undefined behavior\.

In modern languages \(in Rust, for example, thanks to ownership semantics\), all these loops can be successfully optimized\.

### Const, lifetime, and pointer origins

Immutable objects are all well and good except for one thing: they are constant objects in C\+\+\. Once they get stuck somewhere, we can't get them out the normal way\.

What does that mean?

Let's say there's a structure with a constant field:

```cpp
struct Unit {
  const int id;
  int health;
};
```

_Unit_ objects lose the assignment operation because of the constant data member\. They can't be swapped, _std::swap_ doesn't work anymore\. We can't just sort _std::vector<Unit\>_ anymore\.\.\. All in all, it's not convenient at all\.

However, the most exciting part starts if we do something like this:

```cpp
std::vector<Unit> units;
unit.emplace_back(Unit{1, 2});
std::cout << unit.back().id <<  " ";
unit.pop_back();
unit.emplace_back(Unit{2, 3});
std::cout << unit.back().id <<  "";
```

Depending on whether the vector implementation was capable of overcoming the compiler aggressive optimizations, such code may output either _1 2_ \(everything's fine\) or _1 1_ \(the compiler optimized a constant data member\!\)\.

The compiler can interpret the things as follows:

* the vector has just one element;
* the vector didn't reallocate;
* the pointer to the element in both _cout_ statements is the same;
* both use a constant data member;
* I've already read it on the first _cout_ statement;
* why reading it again, it's a constant;
* outputting the cached value\.

Fortunately or not, it's impossible to reproduce such compiler behavior in the real world\. However, code that can be used to implement self\-describing _std::optional_ provokes UB \(and not just one instance\!\) by default:

```cpp
using storage = std::aligned_storage_t<sizeof(Unit), alignof(Unit)>;
storage s;
new (&s) Unit{1,2};
std::cout << reinterpret_cast<Unit*>(&s)->id << "\n"; // UB
reinterpret_cast<Unit*>(&s)->~Unit(); // UB
new (&s) Unit{2,2};
std::cout << reinterpret_cast<Unit*>(&s)->id << "\n"; // UB
reinterpret_cast<Unit*>(&s)->~Unit(); // UB
```

Here's the fixed code:

```cpp
using storage = std::aligned_storage_t<sizeof(Unit), alignof(Unit)>;
storage s;
auto p = new (&s) Unit{1,2};
std::cout << p->id << "\n";
p->~Unit();
p = new (&s) Unit{2,2};
std::cout << p->id << "\n";
p->~Unit();
```

However, supporting a pointer returned by the operator _new_ isn't always possible\. Storing it requires some space, which is inefficient when implementing _optional_: _int32\_t_ takes three times more space on a 64\-bit system \(4 bytes per _storage_ \+ 8 bytes per pointer\)\!

That's why the standard library, starting from C\+\+17, has a function of "laundering" pointers that came from nowhere—_std::launder_\.

```cpp
using storage =
  std::aligned_storage_t<sizeof(Unit), alignof(Unit)>;
storage s;
new (&s) Unit{1,2};
std::cout <<
  std::launder(reinterpret_cast<Unit*>(&s))->id << "\n";
std::launder(reinterpret_cast<Unit*>(&s))->~Unit();
new (&s) Unit{2,2};
std::cout <<
  std::launder(reinterpret_cast<Unit*>(&s))->id << "\n";
std::launder(reinterpret_cast<Unit*>(&s))->~Unit();
```

So, what does _const_ have to do with it? The combination of "real" constancy \(variables and data members declared as _const_\) and UB from using "wrong" pointers enable the compiler to produce the described special effects\.

#### Useful links

1. ISO C\+\+ FAQ\. [What is "const correctness"?](https://isocpp.org/wiki/faq/const-correctness)
1. Mikhail Maltsev\. [std::launder: the most obscure new feature of C\+\+17](https://miyuki.github.io/2016/10/21/std-launder.html)\.
1. Stack Overflow\. [How do I remove code duplication between similar const and non\-const member functions?](https://stackoverflow.com/questions/123758/how-do-i-remove-code-duplication-between-similar-const-and-non-const-member-func) 

## Broken syntax and standard library: overloaded standard library constructors

When designing the C\+\+ standard library, its developers made a lot of controversial decisions that make us suffer\. It's impossible to fix them for backward compatibility reasons\.

One such puzzling decision is overloading constructors with drastically different behavior\.

Here's a good [example](https://gcc.godbolt.org/z/8x3Tojjob):

```cpp
using namespace std::string_literals;
std::string s1 { "Modern C++",  3 };
std::string s2 { "Modern C++"s, 3 };

std::cout << "S1: " << s1 << "\n";
std::cout << "S2: " << s2 << "\n";
```

The code outputs the following:

```cpp
S1: Mod
S2: ern C++
```

That's because _std::basic\_string_ has a single constructor that takes a pointer and the length of the string\. There's also another constructor that takes "something that looks like a string" and the position to extract the substring from it\!

The strange things don't end there\.

```cpp
std::string s1 {'H', 3};
std::string s2 {3, 'H'};
std::string s3 (3, 'H');

std::cout << "S1: " << s1.size() << "\n";
std::cout << "S2: " << s2.size() << "\n";
std::cout << "S3: " << s3.size() << "\n";
```

This [example](https://gcc.godbolt.org/z/o9Yea3aYe) outputs the following:

```cpp
S1: 2
S2: 2
S3: 3
```

This is because the string has a constructor that takes an _n_ number and the _c_ character that needs to be repeated _n_ times\. There's also a constructor that takes an initializer list \(_std::initializer\_list<T\>_\) consisting of characters\. This constructor existence interacts with implicit type casting\!

* _std::string s1 \{'H', 3\};_ — the "H\\3" line
* _std::string s2 \{3, 'H'\};_ — the "\\3H" line
* _std::string s3 \(3, 'H'\);_ — the "HHH" line

The _std::vector_ type has the same problem:

```cpp
std::vector<int> v1 {3, 2}; // v1 == {3, 2}
std::vector<int> v2 (3, 2); // v2 == {2,2,2}
```

Containers also have a constructor that takes a pair of iterators\. It seems like we won't have any issues with them, but we have pointers, which are also iterators\. And then, there's the _bool_ type:

```cpp
bool array[5] = {true, false, true, false, true};
std::vector<bool> vector {array, array + 5};
std::cout << vector.size() << "\n";
```

Only 2 [will be displayed](https://gcc.godbolt.org/z/bvYfc639s), not 5\. This is because pointers are implicitly cast to _bool_\!

Since C\+\+20, converting pointers to _bool_ is now considered narrowing, even in earlier standard versions\. So, the latest compiler versions have [started](https://godbolt.org/z/568T4jzKP) to either issue a warning by default, like GCC:

```cpp
narrowing conversion of '(bool*)(& array)' from 'bool*' to 'bool'.
```

Or, like Clang, they refuse to compile:

```cpp
error: type 'bool *' cannot be narrowed to
'bool' in initializer list [-Wc++11-narrowing]
```

So, these nice examples show why "universal" initialization isn't truly universal\.

To prevent chaos in projects, one should be careful when declaring overloaded constructors for types\. It's better to introduce a static function than to create overloaded constructors that unexpectedly interact with implicit type casting and initialization lists\.

#### Useful links

1. [Converting from T\* to bool should be considered narrowing](https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2020/p1957r2.html)\.



## Broken syntax and standard library: move semantics

Starting with C\+\+11, we have _rvalue_ references and move semantics\. And moving isn't destructive: the original object stays alive thus creating a lot of errors\. There are still issues with how to avoid overhead when using moveable objects, but we can deal with that\.

### Overhead costs

Despite bold claims, abstractions in C\+\+ are far from free\. An interesting example is _std::unique\_ptr_, which is bound to move semantics\.

```cpp
void run_task(std::unique_ptr<Task> ptask) {
  // do something
  ptask->go();
}

void run(...){
  auto ptask = std::make_unique<Task>(....);
  ....
  run_task(std::move(ptask));
}
```

When _run\_task_ is called, the parameter is passed by value: a new _unique\_ptr_ object is created, while the original one remains but is left empty\. Since there are two objects, there are also two calls to the destructor\. In contrast, languages with destructive move semantics, like Rust, would require only one destructor call\. 

We can fix the issue by passing an _rvalue_ reference instead:

```cpp
void run_task(std::unique_ptr<Task>&& ptask) {
  // do something
  ptask->go();
}
```

By passing an _rvalue_ reference, no additional object is created, so there's only one destructor call\. Moreover, this approach introduces an additional level of indirection and memory access because of the reference\.

However, the most important thing is that [there is no actual movement](https://godbolt.org/z/695Pvbcf6), and this can hide a bug in the program logic\.

```cpp
void consume_v1(std::unique_ptr<int> p) {}
void consume_v2(std::unique_ptr<int>&& p) {}

void test_v1(){
  auto x = std::make_unique<int>(5);
  consume_v1(std::move(x));
  assert(!x); // ok
}

void test_v2(){
  auto x = std::make_unique<int>(5);
  consume_v2(std::move(x));
  assert(!x); // fire!
}
```

Running the second function will most likely end with [Access Violation](https://pvs-studio.com/en/blog/terms/0063/) \(SIGSEGV\)\.

It brings us to the main issue\.

### Use\-after\-move

Firstly, the [_std::move_](https://pvs-studio.com/en/blog/terms/6518/) function doesn't do anything\. It simply converts the _lvalue_ reference to an _rvalue_ one\. It doesn't affect the object state in any way\. The functions that operate on _rvalue_ references can produce visible move effects\. Most of them are move constructors and move operators\.

Secondly, the C\+\+ standard doesn't specify the exact state of an object after a move operation\. The only requirement is that the object must remain valid to have the destructor called\. The object doesn't have to be empty after the move operation\. Its data members don't have to be zeroed\. For example, _std::thread_ can't have any of its member functions called after being moved\. And _std::unique\_ptr_ is guaranteed to become empty \(_nullptr_\)\.

The most common and easiest way to run into use\-after\-move is to implement constructors that fill data members with passed arguments\. Just give the same \(or almost the same\) names to data members and arguments\.

```cpp
struct Person {
public:
  Person(std::string first_name,
         std::string last_name) : first_name_(std::move(first_name)),
                                  last_name_(std::move(last_name)) {
      std::cerr << first_name; // wrong, use-after-move
  }
private:
  std::string first_name_;
  std::string last_name_;
};
```

Of course, in such a case, the error will be found quickly, as it's guaranteed that after moving the _std::string_ object, it will be empty\. However, if we make the constructor template\-based and pass trivially movable types to it, the error may not occur for a long time\.

```cpp
template <class T1, class T2>
Person(T1 first_name,
       T2 last_name) : first_name_(std::move(first_name)),
                       last_name_(std::move(last_name))
{
  std::cerr << first_name; // wrong, use-after-move
}
....

Person p("John", "Smith"); // T1, T2 = const char*
```

Another interesting use case in addition to the previous one is self\-move\-assignment, which can result in data suddenly disappearing from an object\. Or they may not disappear\. The outcome depends on how the move operation is implemented for a particular type\.

For example, this naive implementation of the _remove\_if_ algorithm contains an error:

```cpp
template <class T, class P>
void remove_if(std::vector<T>& v, P&& predicate) {
  size_t new_size = 0;
  for (auto&& x : v) {
    if (!predicate(x)) {
      v[new_size] = std::move(x); // self-move-assignment!
      ++new_size;
    }
  }
  v.resize(new_size);
}
```

The [error](https://godbolt.org/z/MY31ed1bj) emerges when container elements contain data members that don't consider self\-assignment\.

```cpp
struct Person {
  std::string name;
  int age;
};

std::vector<Person> persons = {
  Person { "John", 30 }, Person { "Mary", 25 }
};
remove_if(persons, [](const Person& p) { return p.age < 20; });

for (const auto& p : persons){
  std::cout << p.name << " " << p.age << "\n";
}
```

If we compile and run this code, we can make sure that all _name_ are empty:

```cpp
30
25
```

Static analyzers can track some post\-move use cases\. For example, PVS\-Studio has the [V1030](https://pvs-studio.com/en/docs/warnings/v1030/) diagnostic rule, and Clang\-Tidy has [bugprone\-use\-after\-move](https://clang.llvm.org/extra/clang-tidy/checks/bugprone/use-after-move.html)\.

If you're implementing movable classes and want to consider the possibility of self\-assignment/self\-move, either use the [copy/move\-and\-swap idiom](https://mropert.github.io/2019/01/07/copy_swap_20_years/), or don't forget to check if the addresses of the current and moved objects match:

```cpp
MyType& operator=(MyType&& other) noexcept {
  if (this == std::addressof(other)) { // addressof works
                                       // if you have an overloaded &
    return *this;
  }
  ....
}
```

#### Useful links

1. Clang\-tidy documentation\. [bugprone\-use\-after\-move](https://clang.llvm.org/extra/clang-tidy/checks/bugprone/use-after-move.html)\.
1. CppCon 2019: [Chandler Carruth "There Are No Zero\-cost Abstractions"](https://www.youtube.com/watch?v=rHIkrotSwcc&t=1065s)\. 
1. Stack Overflow\. [What can I do with a moved\-from object?](https://stackoverflow.com/questions/7027523/what-can-i-do-with-a-moved-from-object)
1. Herb Sutter\. [Move, simply](https://herbsutter.com/2020/02/17/move-simply/)\. 
1. Mathieu Ropert\. [Copy and Swap, 20 years later](https://mropert.github.io/2019/01/07/copy_swap_20_years/)\. 

## Broken syntax and standard library: std::enable\_if\_t vs\. std::void\_t

C\+\+ templates, which began as a more refined version of copying\-and\-pasting with preprocessor macros and evolved with [SFINAE](https://en.wikipedia.org/wiki/Substitution_failure_is_not_an_error) rules, have spawned some pretty creepy, cumbersome, but powerful features for metaprogramming and compile\-time computations\.

The mechanism is highly useful yet extremely inconvenient\. It's no surprise that the standard library has introduced different quality\-of\-life tools\.

The details of how SFINAE works go far beyond the scope of this book\. Here, we'll discuss things that were introduced in C\+\+17; things that should have made coding easier but often fall short of expectations\.

In short, the SFINAE \(substitution failure is not an error\) rule goes as follows:

* if an error occurs when substituting arguments into the template **header** \(resulting in an invalid construct\), the template is ignored, and the compiler tries the next suitable template;
* If there are no more suitable templates, a compilation error occurs\.

Take a look at the example:

```cpp
#include <type_traits>

template <class T>
decltype(void(std::declval<typename T::Inner>())) fun(T) { // 1
  std::cout << "f1\n";    
}

template <class T>
decltype(void(std::declval<typename T::Outer>())) fun(T) { // 2
  std::cout << "f2\n";    
}

struct X {
  struct Inner {};
};

struct Y {
  struct Outer {};
};

....

fun(X{}); // when substituted into template 2
          // the X::Outer construct is invalid: 
          // X has no such type. It's discarded.
          // Template 1 substitution
          // goes without errors, "f1" is displayed.

fun(Y{}); // the same thing but in reverse.
          // Y::Inner doesn't exist. Displays "f2"
```

The _decltype\(void\(void\(std::declval<typename T::Outer\>\)\)_ construct used for "pattern matching" is terrible\. The dark minds of the C\+\+ masters have created even creepier things\. However, for a less savvy user, it would be nice to have something simpler, more comprehensible, and user\-friendly\.

So, we have _std::enable\_if\_t_ that allows us to trigger SFINAE not by a creepy custom construct but by a Boolean value\.

```cpp
template<class T>
std::enable_if_t<sizeof(T) <= 8> process(T) {
  std::cout << "by value";
}

template<class T>
std::enable_if_t<sizeof(T) > 8> process(const T&) {
  std::cout << "by ref";
}
...
process(5); // by value
const std::vector<int> v;
process(v); // by ref
```

Indeed, we can still use horrendous constructs in the _std::enable\_if_ argument, not just some predicates\.

```cpp
template <class T>
std::enable_if_t<std::is_same_v<typename T::Inner, typename T::Inner>> 
fun(T) { // 1
  std::cout << "f1\n";    
}

template <class T>
std::enable_if_t<std::is_same_v<typename T::Outer, typename T::Outer>> 
fun(T) { // 2
  std::cout << "f2\n";    
}

fun(X{}); // even though the std::is_same_v<T, T> value
          // is always true, X::Outer doesn't exist.
          // SFINAE is triggered not because of the predicate value
          // but because of its arguments.
```

This is where the first trouble begins: _std::enable\_if_ vs\. _std::enable\_if\_t_\.

```cpp
// approximately
template <bool cond, T = void>
struct enable_if {};

template <true, T = void>
struct enable_if {
  using type = T;
};

template <bool cond, T = void>
using enable_if_t = typename enable_if<cond, T>::type;
```

You can easily mix them up when typing quickly with auto\-complete\. Omitting the _\_t_ suffix by mistake, causing many hours of debugging will be lost along with all that pattern\-matching things:

```cpp
// SFINAE is triggered by the predicate value and no longer works.
// std::enable_if<false> is a valid type.
// We get UB due to redefining the same entity
// in different ways
template<class T>
std::enable_if<sizeof(T) <= 8> process(T);
template<class T>
std::enable_if<sizeof(T) > 8> process(const T&);


// SFINAE is triggered by the predicate arguments and
// continues to operate.
// If you expected void as the return type,
// there may be UB because of the missing return.
template <class T>
std::enable_if<std::is_same_v<
  typename T::Inner, typename T::Inner>> fun(T);
template <class T>
std::enable_if<std::is_same_v<
  typename T::Outer, typename T::Outer>> fun(T);
```

The same applies to all the other mysterious creatures in the _<type\_traits\>_ header\. Each _std::trait\_X_ and _std::trait\_X\_t_ are types that aren't always seen when mixed up\.

As a rule of thumb, it's better to use _std::enable\_if_ to trigger SFINAE only via the predicate, there're less issues that way\.

If a predicate doesn't exist, you can write one:

```cpp
template <class T, 
          class = void> // the placeholder workaround for the checked "pattern" 
struct has_inner_impl : std::false_type {};

template <class T> 
struct has_inner_impl<T,
   // the "pattern", the result type should be the same
   // as the one specified in the placeholder above
   decltype(void(std::declval<typename T::Inner>()))>
   : std::true_type {};

template <class T>
constexpr bool has_inner_v = has_inner_impl<T>::value;

static_assert(has_inner_v<X>);
static_assert(!has_inner_v<Y>);
```

This is one of the most common and " straightforward" approaches to writing such predicates\. The _void_ pointer is most often used as a kludge placeholder\. To avoid writing this terrible _decltype\(void\(void\(std::declval<X\>\(\)\)\)_ for checking if the X type is valid, developers introduced the _std::void\_t_ template in C\+\+17\.

Here it is:

```cpp
template <class...>
using void_t = void;
```

It should make things shorter and prettier:

```cpp
template <class T> 
struct has_inner_impl<T,
  std::void_t<typename T::Inner>> 
  : std::true_type {};
```

Unfortunately, most of the time it doesn't work as expected\. A [defect](https://wg21.cmeerw.net/cwg/issue1558) \(issue 1558 — WG21 CWG Issues\), which has been found in the C\+\+11 standard, allows this design to fail\. In many not\-so\-brand\-new compiler versions, such a predicate always returns true\.

However, _std::void\_t_ is also broken in newer versions\. Let's try using it to rewrite the very first example in this section:

```cpp
template <class T>
std::void_t<typename T::Inner> fun(T) {
  std::cout << "f1\n";
}

template <class T>
std::void_t<typename T::Outer> fun(T) {
  std::cout << "f2\n";
}
```

[None](https://godbolt.org/z/5q64cPEbG) of the three major compilers \(GCC, Clang, MSVC\) will compile this code\. Despite the fact that the first version with ugly _decltype_ [compiled](https://godbolt.org/z/8fEjbnche)\.

This is because we have the concepts of "equivalence" and "functional equivalence" of declarations\. The compiler checks the former, and the latter has to do with SFINAE\. Here's a [scary thing](https://www.open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#1980)\.

<details>
   <summary>A qoute from "C\\\+\\\+ Standard Core Language Active Issues, Revision 114"\\\.</summary>

1980\. Equivalent but not functionally\-equivalent redeclarations\. In an example like:

```cpp
template<typename T, typename U> using X = T;
template<typename T> X<void, typename T::type> f();
template<typename T> X<void, typename T::other> f();
```

it appears that the second declaration of f is a redeclaration of the first but distinguishable by SFINAE, i\.e\., equivalent but not functionally equivalent\.

Notes from the November, 2014 meeting: CWG felt that these two declarations should not be equivalent\.


</details>
Here's a general tip: don't use _std::void\_t_\. Also, if nothing depends on template\-alias parameters to the right of _using \=_, don't try to build SFINAE on them\.

```cpp
template <class T>
struct my_void {
  using type = void;
}

template <class T>
using my_void_t = void; // doesn't work

template <class T>
using my_void_t = typename my_void<T>::type; // ok
```

It's even better to switch to C\+\+20 and avoid dealing with all this nonsense\. There's a readable syntactic sugar created specifically for all those scary constructs\. Of course, it has some hidden pitfalls, but that's a topic for another time\.

#### Useful links

1. Cppreference\. [Function template](https://en.cppreference.com/w/cpp/language/function_template)\.
1. Cppreference\. [std::void\_t](https://en.cppreference.com/w/cpp/types/void_t)\.

## Broken syntax and standard library: forgotten return

Sometimes people refer to C and C\+\+ as languages with a special syntax for writing invalid programs\.

In C and C\+\+, a function that returns something other than _void_ doesn't necessarily have to be of the _return\-something_ kind\.

```cpp
int add(int x, int y) {
  x + y;
}
```

This is a syntactically correct function that results in undefined behavior\. The result could be a garbage, there may be an insertion in the code of the next function below, or [everything may be fine](https://gcc.godbolt.org/z/6Y4T66)\.

<details>
   <summary>A beautiful example of "everything is fine"\\\.</summary>

This is what happens if we build this code:

```cpp
#include <stdio.h>

int f(int a, int b) {
  int c = a + b;
}

int main() {
  int x = 5, y = 6;
  printf("f(%d,%d) is %d\n", x, y, f(x,y));
  return 0;
}
```

For example, if you are using GCC 5\.2, then the sum will be "calculated" and the program will display the following:

```cpp
f(5,6) is 11
```

However, don't think that undefined behavior for such code is only about whether to display a correct or random value\. A missing _return_ [may cause](https://pvs-studio.com/en/blog/posts/cpp/0917/) the application to crash, just like the code above\. All we need to do is change the compiler to GCC 14\.1, and the program execution result [will be](https://gcc.godbolt.org/z/oMbv3oMxW):

```cpp
Program terminated with signal: SIGILL
```


</details>
This confusion can be especially painful for those who came to C\+\+ from an expression\-oriented language where such code is perfectly fine:

```cpp
fn add(x: i32, y: i32) -> i32 {
  x + y
}
```

These are few reasons why we don't need to write _return_ at the end of the function:

1. A function can have branching logic, where certain branches might not require a return statement\. For example, the code might involve an infinite loop, an exception, a call to _std::exit_, _std::longjmp_, or something else marked with the _\[\[noreturn\]\]_ attribute\. We can't always check for such code\.
1. The function might contain an assembler insert with special finalization code and the _ret_ instruction\.

Of course, we can check if there is a formal _return_\. However, we were allowed to omit the formal line in certain rare cases, and compilers were allowed not to treat it as a bug\.

In several years of working on large projects combining C\+\+, Rust, and Kotlin \(two languages with optional _return_\!\), I've seen quite a few forgotten _return_s\. And I've forgotten them too sometimes\. In particular, _return_ seems to be lost in custom assignment operators\. After all, _operator \=_ is probably one of the few _non_\-_void_ functions the result of which is almost always ignored\.

With the _\-Wreturn\-type_ flag, GCC and Clang report the issue in many cases\. Analyzers such as PVS\-Studio also issue warnings\. By the way, if we look at the bug collection gathered by the PVS\-Studio team, we can see that the lack of _return_ isn't some kind of an exotic bug, but a [very common one](https://pvs-studio.com/en/blog/examples/v591/)\. So, be vigilant\.

The only exception, starting from C\+\+11 \(or C99, if we talk about pure C code\), is the _main_ function\. In this case, a missing _return_ doesn't lead to undefined behavior and is treated as a return of 0\. 

#### Useful links

1. Stack Overflow\. [Why does flowing off the end of a non\-void function without returning a value not produce a compiler error?](https://stackoverflow.com/questions/1610030/why-does-flowing-off-the-end-of-a-non-void-function-without-returning-a-value-no)
1. Cppreference\. [C\+\+ attribute: noreturn](https://en.cppreference.com/w/cpp/language/attributes/noreturn)\.
1. Andrey Karpov\. [An example of undefined behavior caused by absence of return](https://pvs-studio.com/en/blog/posts/cpp/0917/)\.

**Author: Dmitry Sviridkin**

Dmitry has over eight years of experience in high\-performance software development in C and C\+\+\. From 2019 to 2021, Dmitry Sviridkin has been teaching Linux system programming at SPbU and C\+\+ hands\-on courses at HSE\.  Currently works on system and embedded development in Rust and C\+\+ for edge servers as a Software Engineer at AWS \(Cloudfront\)\. His main area of interest is software security\. 

**Editor: Andrey Karpov**

Andrey has over 15 years of experience with static code analysis and software quality\. The author of numerous articles on writing high\-quality code in C\+\+\. Andrey Karpov has been honored with the Microsoft MVP award in the Developer Technologies category from 2011 to 2021\. Andrey is a co\-founder of the PVS\-Studio project\. He has long been the company's CTO and was involved in the development of the C\+\+ analyzer core\. Andrey is currently responsible for team management, personnel training, and DevRel activities\.

## All chapters

1. [Part 1](https://pvs-studio.com/en/blog/posts/cpp/1129/): introduction; what is undefined behavior and what it leads to; narrowing conversions and implicit type conversion\.
1. [Part 2](https://pvs-studio.com/en/blog/posts/cpp/1136/): overflow of signed integers; floating\-point numbers; integer promotion; _char_ and sign extension\.
1. [Part 3](https://pvs-studio.com/en/blog/posts/cpp/1149/): dangling references; _string\_view_; a fly in the syntactic sugar \(range\-based for\); self\-reference; _std::vector_ and reference invalidation\.
1. [Part 4](https://pvs-studio.com/en/blog/posts/cpp/1156/): lambda function capture lists; tuples; unexpected mutability; implicit references; use\-after\-move; lifetime extension\.
1. [Part 5](https://pvs-studio.com/en/blog/posts/cpp/1160/): Most Vexing Parse; non\-constant constants; move semantics; _std::enable\_if\_t_ vs\. _std::void\_t_; forgotten _return_\.
1. [Part 6](https://pvs-studio.com/en/blog/posts/cpp/1163/): ellipsis and functions; _operator \[\]_; _iostreams_—good luck debugging\!; comma operator; function\-try\-block; zero\-sized types\.
1. [Part 7](https://pvs-studio.com/en/blog/posts/cpp/1174/): null\-terminated strings; _std::shared\_ptr_; ~~im~~explicit type conversion; how to pass a standard function and not break anything\.
1. [Part 8](https://pvs-studio.com/en/blog/posts/cpp/1178/): infinite loops and halting problem; recursion; false _noexcept_; buffer overflow\.
1. [Part 9](https://pvs-studio.com/en/blog/posts/cpp/1182/): \(N\)RVO vs RAII; null pointer dereferencing; static initialization order fiasco; static inline; ODR violation; reserved names\.
1. [Part 10](https://pvs-studio.com/en/blog/posts/cpp/1193/): trivial types and ABI; uninitialized variables; C\+\+20 unbounded ranges; non\-virtual yet virtual functions; VLA\.
1. [Part 11](https://pvs-studio.com/en/blog/posts/cpp/1199/): invalid pointers; placement new for arrays; data race; mutex deadlock; signal \(un\)safety; how to do everything right and trigger the deadlock\.
1. [Part 12](https://pvs-studio.com/en/blog/posts/cpp/1211/): _std::vector::reserve_ and _std::vector::resize_; unaligned references; time of life and death; static analysis and UB; conclusion\.