WTF C++

30/08/2020 updated on 06/10/2023  │ Notes

In this page, I enumerate various C++ fragments that sparkled my incomprehension when I encountered them, or made me scratch my head far too long. It is meant to be a reminder mainly for myself and destined to be updated over time.


No implicit conversion for arrays of 1 element

1
2
std::array<int,1> arr {42};
int v = arr; // ❌ That's a no-no 

Filesystem library and operator /

Operator / does not always act relative to the previous path… A leading slash is always considered as referring to the root, therefore the input path is considered absolute instead of relative. Moral of the story: omit it or do not forget the leading dot!

1
2
3
4
5
6
7
8
#include <filesystem>
namespace fs = std::filesystem;
int main() {
    fs::path some_path = fs::path("foo"); 
    fs::path p1 = some_path / "bar";  // the result is "foo/bar" (appends)
    fs::path p2 = some_path / "/bar"; // the result is "/bar" (replaces)
    fs::path p3 = some_path / "./bar";// the result is "foo/./bar" (appends)
}

Initialization of std::vector

1
2
std::vector<size_t, N> indices(N);// creates a vector of size N
std::vector<size_t, N> indices{N};// creates a vector of size 1 containing N

Template template arguments, type or non-type argument headache

Template template arguments were made to enable templated classes/methods to depend on templated classes, for example imagine wanting to swap of hash map implementation. Unfortunately, a non-type template parameter and a type template parameter are not handled similarly by compilers and they cannot be mixed. Indeed, a pack typename... will expect only type template argument, while auto... will expect only non-type template arguments.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
template<template<typename... /**/> typename Map>
struct Foo { Map<int,int> map; };

template<typename A, typename B, typename C = int>
struct Map_A {};
template<typename A, typename B, bool C = false>
struct Map_B {};

Foo<Map_A> foo_a{};
Foo<Map_B> foo_b{};//fails on GCC 10, Clang 10 and VS2019

The above snippet leads to errors, while descriptive, can leave the user easily clueless: “type/value mismatch at argument N in template parameter list for…” for GCC, and “template template argument has different template parameters than its corresponding template template parameter” for Clang.

Trying to circumvent the problem by enforcing only 2 template parameters and leaving out the default parameters, which should be valid in C++17, will fail on Clang 10 unless the flag -frelaxed-template-template-args is provided or for clang on windows /clang:-frelaxed-template-template-args.

1
2
3
4
template<template<typename,typename> typename Map>
struct Bar { Map<int,int> map; };
Bar<Map_A> bar_a{};//fails on Clang 10
Bar<Map_B> bar_b{};//fails on Clang 10

std::forward a lvalue reference leads to move

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
#include<utility>
#include<iostream>
struct X {
    X()    = default;
    X(const X&) {std::cout<<"copy\n";}
    X& operator=(const X&) {std::cout<<"copy\n";}
    X(X&& other){std::cout<<"move\n";}
    X& operator=(X&& other){std::cout<<"move\n";}
};
struct Y {X x;};

template<typename T> Y create_from_ref(T& r) {return Y{std::forward<T>(r)};}
int main () {
    X x;
    Y y = create_from_ref(x);
    return x.i;
}

Factory functions (make_*) and initializer lists

Compilers cannot deduce the final type of a brace-enclosed initializer lists from the delegated constructor.

1
2
3
4
5
6
7
struct Foo 
{
	Foo() = default
	Foo(std::vector<double>){}
};
std::unique_ptr<Foo> foo;
foo = std::make_unique<Foo>({3.14});// ❌ That's a no-no 

error: no matching function for call to make_unique<Foo>(<brace-enclosed initializer list>)


std::span cannot be initialized from initializer lists

1
2
void foo(std::span<int>){}
foo({1,2,3});

error: could not convert ‘{1, 2, 3}’ from ‘’ to ‘std::span

But there is hope for C++26 with proposal P2447


Overload resolution between const& and unconstrained template T&&

Due to overload resolution rules, the unconstrained template version will be preferred with a L-value std::string& and R-value string&& argument over the standard catch-all const&. The forwarding reference would be resolved exactly for those argument type unlike the const reference variant, which is chosen later in the overload selection.

1
2
3
4
5
6
7
8
9
void foo(const std::string&) { std::cout << "string version called" << std::endl; }

template<class T>
void foo(T&&) { std::cout << "template version called" << std::endl; }

void main() 
{
    foo("bar"); // prints "template version called"
}