ArticlesScrapsPagesAbout

.at()のラッパー関数を作るのが難しい

C++のコンテナ型の.at()は頻繁に呼ばれる関数であるにも拘らず例外から得られる情報が少ない。 ので、ラッパー関数を作りたい。 このとき、返戻値の型周辺で面倒事が起こる。

value_typeで定義すると、std::vectorは良いが、std::unordered_mapは型の違いによりコンパイルエラーが生じる。std::unordered_map<U, V>value_typestd::pair<const U, V>であり、.at()の返戻値の型Vと異なるためである。同様に、mapped_typeで定義すると、std::unordered_mapは良いが、std::vectorには定義されていないためコンパイルエラーが生じる。

#include <format>
#include <string>
#include <unordered_map>

template<typename T, typename U>
const typename T::value_type &at(const T &c, const U &k, const std::string &s) {
	try {
		return c.at(k);
	} catch (const std::out_of_range &) {
		throw std::out_of_range(std::format("the key '{}' is invalid for {}.", k, s));
	}
}

int main() {
	const std::unordered_map<int, int> m;
	const auto &a = at(m, 0, "m");
	return 0;
}

autoで定義すると、参照ではなく値を返す関数とみなされ、参照として受け取れない。

#include <format>
#include <string>
#include <unordered_map>

template<typename T, typename U>
auto at(const T &c, const U &k, const std::string &s) {
	try {
		return c.at(k);
	} catch (const std::out_of_range &) {
		throw std::out_of_range(std::format("the key '{}' is invalid for {}.", k, s));
	}
}

int main() {
	const std::unordered_map<int, int> m;
	const auto &a = at(m, 0, "m"); // 一時オブジェクトの参照取得によりコンパイルエラー
	return 0;
}

const auto &decltype(c.at(k))で定義すると、Ubuntuのg++の-Wextraでは参照ではなく値を返す関数とみなされ、参照として受け取れない。CIでbuild-essentialのみインストールして警告を強めに出しているいる場合は詰むことになる。

// g++ --std=c++20 -Wall -Werror -Wextra main.cpp
#include <unordered_map>

template<typename T, typename U>
const auto &at1(const T &c, const U &k) {
	try {
		return c.at(k);
	} catch (...) {
		throw "at1";
	}
}

template<typename T, typename U>
auto at2(const T &c, const U &k) -> decltype(c.at(k)) {
	try {
		return c.at(k);
	} catch (...) {
		throw "at2";
	}
}

int main() {
	const std::unordered_map<int, int> m;
	const auto &a = at1(m, 0); // 一時オブジェクトの参照取得によりコンパイルエラー
	const auto &b = at2(m, 0); // 一時オブジェクトの参照取得によりコンパイルエラー
	(void)a;
	(void)b;
	return 0;
}

std::invoke_result_tで定義すると、.at()がconst版と非const版でオーバーロードされているために型を推論できずコンパイルエラーが生じる。

#include <format>
#include <string>
#include <type_traits>
#include <unordered_map>

template<typename T, typename U>
std::invoke_result_t<decltype(&T::at), U> at(const T &c, const U &k, const std::string &s) {
	try {
		return c.at(k);
	} catch (const std::out_of_range &) {
		throw std::out_of_range(std::format("the key '{}' is invalid for {}.", k, s));
	}
}

int main() {
	const std::unordered_map<int, int> m;
	const auto &a = at(m, 0, "m"); // 一時オブジェクトの参照取得によりコンパイルエラー
	return 0;
}