Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Multiple return types

It's possible to return multiple types from a function in C++ by using exceptions! Unfortunately, it turns out that C++ exceptions are very slow. How slow? Well…

  • variant and union both run in about 7ms
  • exn runs in about 1010ms

So using exceptions as control flow in C++ is about 140x slower.

#include <string>
#include <chrono>
#include <iostream>
using Clock = std::chrono::high_resolution_clock;
void return_item(int counter) {
if (counter % 2 == 0) {
throw counter;
} else {
throw std::string("hey!");
}
}
int main() {
int total = 0, total2 = 0;
auto start = Clock::now();
for (int i = 0; i < 1000000; i++) {
try {
return_item(i);
} catch (int i) {
total += i;
} catch (std::string s) {
total2 += s.size();
}
}
auto end = Clock::now();
auto ns = std::chrono::duration_cast<std::chrono::nanoseconds>(end - start).count();
std::cout << total << " " << total2 << " in " << ns << "ns" << std::endl;
return 0;
}
all: union exn variant
%: %.cpp
clang++ -Wall -Wextra -Werror -std=c++17 -O3 $< -o $@
clean:
rm -f union exn variant
#include <string>
#include <chrono>
#include <iostream>
using Clock = std::chrono::high_resolution_clock;
enum Tag {
STRING,
INT,
};
union Result {
struct {
Tag tag;
} t;
struct {
Tag tag;
std::string value;
} s;
struct {
Tag tag;
int value;
} i;
Result(const Result &res) {
t.tag = res.tag();
switch (tag()) {
case STRING:
s.value = res.s.value;
break;
case INT:
i.value = res.i.value;
break;
}
}
Result(std::string value) {
s.tag = STRING;
s.value = value;
}
Result(int value) {
i.tag = INT;
i.value = value;
}
~Result() {
switch (tag()) {
case STRING:
s.value.~basic_string();
break;
case INT: break;
}
}
Tag tag() const {
return t.tag;
}
};
Result return_item(int counter) {
if (counter % 2 == 0) {
return Result(counter);
} else {
return Result("hey!");
}
}
int main() {
int total = 0, total2 = 0;
auto start = Clock::now();
for (int i = 0; i < 1000000; i++) {
Result item = return_item(i);
switch (item.tag()) {
case STRING:
total2 += item.s.value.size();
break;
case INT:
total += item.i.value;
break;
}
}
auto end = Clock::now();
auto ns = std::chrono::duration_cast<std::chrono::nanoseconds>(end - start).count();
std::cout << total << " " << total2 << " in " << ns << "ns" << std::endl;
return 0;
}
#include <string>
#include <chrono>
#include <iostream>
#include <variant>
using Clock = std::chrono::high_resolution_clock;
template<class... Ts> struct overloaded : Ts... { using Ts::operator()...; };
template<class... Ts> overloaded(Ts...) -> overloaded<Ts...>;
std::variant<int, std::string> return_item(int counter) {
if (counter % 2 == 0) {
return counter;
} else {
return "hey!";
}
}
int main() {
int total = 0, total2 = 0;
auto start = Clock::now();
for (int i = 0; i < 1000000; i++) {
auto item = return_item(i);
std::visit(overloaded {
[&](int i) { total += i; },
[&](std::string s) { total2 += s.size(); },
}, item);
}
auto end = Clock::now();
auto ns = std::chrono::duration_cast<std::chrono::nanoseconds>(end - start).count();
std::cout << total << " " << total2 << " in " << ns << "ns" << std::endl;
return 0;
}
@artemkin

This comment has been minimized.

Copy link

artemkin commented Sep 26, 2019

@porglezomp

This comment has been minimized.

Copy link
Owner Author

porglezomp commented Sep 26, 2019

Ah, nice! I get similar performance just by deleting the copy constructor from my implementation.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.