読者です 読者をやめる 読者になる 読者になる

変数テンプレートをラムダ式でキャプチャするときの注意

C++

Variable templates + generic lambdas for std::map

C++14で導入される予定の変数テンプレートですが、ラムダ式でその変数をキャプチャするときには注意が必要です。

まだインスタンス化されていない型の変数テンプレートは、ラムダ式でキャプチャしても使用できません。

以下のプログラムでは、ラムダ式の参照キャプチャで変数テンプレートstorageをキャプチャすることを意図していますが、ラムダ式を定義した時点でstorageはどの型に対してもインスタンス化されていないため、キャプチャされません。

Clang 3.4では、これはリンクエラーになります。

#include <map>
#include <string>

template <typename T>
std::map<int, T> storage;

void some_func() {
    auto store = [&](auto pair) { storage<decltype(pair.second)>.insert(pair); };

    store(std::pair<int, int>(0, 1));
    store(std::pair<int, std::string>(1, "Hello!"));
    store(std::pair<int, int>(2, 3));
}

int main()
{
}

リンクエラーのメッセージ(読みやすいように整形してます):

/tmp/prog-d14554.o: In function `some_func()::lambda(pair<int, int>)': prog.cc: undefined reference to `storage<int>'
/tmp/prog-d14554.o: In function `some_func()::lambda(pair<int, string>)': prog.cc: undefined reference to `storage<string>'
clang: error: linker command failed with exit code 1 (use -v to see invocation)

変数テンプレートをラムダ式でキャプチャする場合は、ラムダ式で使用するテンプレート引数の型で、事前にインスタンス化しておく必要があります:

#include <map>
#include <string>

template <typename T>
std::map<int, T> storage;

template <>
std::map<int, int> storage<int>;

template <>
std::map<int, std::string> storage<std::string>;

void some_func() {
    auto store = [&](auto pair) { storage<decltype(pair.second)>.insert(pair); };

    store(std::pair<int, int>(0, 1));                // OK
    store(std::pair<int, std::string>(1, "Hello!")); // OK
    store(std::pair<int, int>(2, 3));                // OK
}

int main()
{
}