Consider the following code:
#include <unordered_map>
#include <iostream>
template<template<class, class, class...> class Map, typename Key, typename T, typename KeyP, typename... Args>
const T& getMapEntry(const Map<Key, T, Args...>& map, const KeyP& key, const T& defval) {
static_assert(std::is_convertible_v<KeyP, Key>);
auto i = map.find(key);
return i != map.end() ? i->second : defval;
}
template<template<class, class, class...> class Map, typename Key, typename T, typename KeyP, typename... Args>
T getMapEntry(const Map<Key, T, Args...>& map, const KeyP& key, T&& defval) {
static_assert(std::is_convertible_v<KeyP, Key>);
auto i = map.find(key);
return i != map.end() ? i->second : std::move(defval);
}
template<template<class, class, class...> class Map, typename Key, typename T, typename KeyP, typename... Args>
T getMapEntry(const Map<Key, T, Args...>& map, const KeyP& key, const T&& defval) = delete;
class Foo {
public:
Foo() { std::cout << __PRETTY_FUNCTION__ << '\n'; }
~Foo() { std::cout << __PRETTY_FUNCTION__ << '\n'; }
Foo(const Foo&) { std::cout << __PRETTY_FUNCTION__ << '\n'; }
Foo& operator=(const Foo&) { std::cout << __PRETTY_FUNCTION__ << '\n'; return *this; }
Foo(Foo&&) { std::cout << __PRETTY_FUNCTION__ << '\n'; }
Foo& operator=(Foo&&) { std::cout << __PRETTY_FUNCTION__ << '\n'; return *this; }
};
int main() {
std::unordered_map<int, Foo> map;
auto&& ret = getMapEntry(map, 123U, Foo{});
return 0;
}
gcc outputs:
Foo::Foo()
Foo::Foo(Foo&&)
Foo::~Foo()
Foo::~Foo()
clang outputs:
Foo::Foo()
Foo::Foo(const Foo &)
Foo::~Foo()
Foo::~Foo()
Using an if/else chain always invokes the move ctor. Using the following line, which is more revealing, also causes the move ctor to be invoked:
return i != map.end() ? T{i->second} : std::move(defval);
Which implementation is correct?
The question surrounds what happens during:
The expression:
per [expr.cond]
Will attempt to find an implicit conversion sequence between
i->secondandstd::move(defval)(both directions).i->second(expression 1, or E1) is an lvaluestd::move(defval)(expression 2 or E2) is an xvalue[expr.cond 4.2] states:
And this almost applies; we'd allow an lvalue to rvalue conversion only if the lvalue could be bound without invoking a copy. Unfortunately this rule cannot be used becaue
i->secondcannot be converted to an rvalue without a copy, so we fall back to [4.3.3] which states:Meaning the return type will be a prvalue, and we finally have to deal with lvalue-to-rvalue conversion of
i->second, which necessitates a copy, per [conv.lval] (an xvalue is also a glvalue, which allows this conversion to apply)So we're ultimately returning an prvalue, which has mandatory copy-elision. This is why there is not yet-another constructor call logged to the console.
Therefore the correct behavior in this case is to see a call to the copy-constructor of
Foo, and clang is correct.If you want to enforce that move construction happens in your case, you cannot use a ternary expression:
This will ensure that both compilers call the move constructor of
Fooif theelsebranch is invoked.