Skip to content

Instantly share code, notes, and snippets.

@porglezomp
Created September 23, 2019 04:52
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save porglezomp/563b9275ccc67da13d1fed0ac518455e to your computer and use it in GitHub Desktop.
Save porglezomp/563b9275ccc67da13d1fed0ac518455e to your computer and use it in GitHub Desktop.
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
Copy link

@porglezomp
Copy link
Author

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