6

An answer to C++14 Variable Templates: what is the purpose? Any usage example? proposes a usage example of variable templates + generic lambdas that would look something like this:

void some_func() {
    template<typename T>
    std::map<int, T> storage;

    auto store = []<typename T>(int key, const T& value) { storage<T>.insert(key, value) };

    store(0, 2);
    store(1, "Hello"s);
    store(2, 0.7);

    // All three values are stored in a different map, according to their type. 
}

Unfortunately it doesn't compile so I've tried to "fix" it and this is my attempt so far.

#include <map>

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

void some_func() {

    auto store = [](int key, const auto& value) { storage<decltype(value)>.insert(key, value); };

    store(0, 2);
    store(1, std::string("Hello"));
    store(2, 0.7);
}

The error message are:

main.cpp:7:76: error: no matching member function for call to 'insert'

    auto store = [](int key, const auto& value) { storage<decltype(value)>.insert(key, value); };
                                                  ~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~
main.cpp:10:10: note: in instantiation of function template specialization 'some_func()::<anonymous class>::operator()<std::basic_string<char> >' requested here

    store(1, std::string("Hello"));

When you instantiate a variable template, like all templates, each variable will be a different type. The theory is auto doesn't get deduced for each type, but only one type initially (double). Therefore the code is invalid. Even if this could work, each instantiation of storage would refer to a different variable.

How can this code be rewritten to achieve the original intent?


Edit I made a small mistake in my edit (see revision history to avoid wall of text.) decltype(pair) should be decltype(pair.second) since there is only one template argument for storage.

#include <map>

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()
{
}

There are now linker errors.

/tmp/main-5f1f7c.o: In function `some_func()':
main.cpp:(.text+0x1a): undefined reference to `storage<int>'
main.cpp:(.text+0x43): undefined reference to `storage<std::string>'
main.cpp:(.text+0x74): undefined reference to `storage<int>'

In order to fix the linker errors, I think you need to explicitly instantiate the arguments? (I'm not even sure if that's the correct term here.)

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

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

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

Live Example

Community
  • 1
  • 1
  • 1
    Can you describe at a high level what the original intent actually is? That is, what is the idea we are trying to implement using code like this? – John Zwinck Jan 17 '14 at 14:24
  • Also have you made sure that your compiler supports variable templates? – PlasmaHH Jan 17 '14 at 14:25
  • @PlasmaHH I'm using [Coliru](http://coliru.stacked-crooked.com/) which has clang 3.5. –  Jan 17 '14 at 14:26
  • I think the problem is: That is not a capturing lambda, so the map (Even if its a global variable) is not being captured by the lambda. – Manu343726 Jan 17 '14 at 14:35
  • 1
    Lambda signature is right??? Shouldn't the lambda be: `auto store = [&] (decltype(storage)::value_type val) { storage.insert(val); };` – Brandon Jan 17 '14 at 14:40
  • @CantChooseUsernames I think you could do `[&](decltype(storage)::value_type val)`, but for some reason, that syntax won't compile. –  Jan 17 '14 at 14:45
  • That would be the incorrect signature though. See here: http://coliru.stacked-crooked.com/a/01d73c0fdaf0ead1 The radius example is taken from open-std.. It does not compile so I'd assume that clang++ does NOT support template variables yet. However, take a look at the function signature I commented out. That would work if clang++ supports template variables. Not sure that it does atm. – Brandon Jan 17 '14 at 15:04
  • @CantChooseUsernames I see a linker error, not a compiler error. You need to do the `template <> constexpr int pi = int(3.1415926535897932385); ` like I posted in my edit, but I'm not sure if that's even the correct syntax. –  Jan 17 '14 at 17:11
  • You're right but then that just gets rid of the point of the template variable imo.. To get rid of the template parameter by explicitly specifying a type.. might as well get rid of the template. It's useful though because then you have one variable name representing many different types I guess. Btw, I got the example I had from: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3651.pdf – Brandon Jan 17 '14 at 17:18
  • @Manu343726 Beginning with the second example, the `map` is a global. Globals don't have to be captured; only automatic variables and `this`. – dyp Jan 20 '14 at 12:56

1 Answers1

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

This is a declaration, much like e.g. template<typename T> class foo;. As we need one anyway, we would benefit from making it a definition as well:

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

This doesn’t get rid of the linker errors however, suggesting there’s an outstanding bug with implicit instantiation. To convince ourselves, we can trigger instantiation in various ways:

  • explicitly, which would look like

    // namespace scope, same as storage
    template /* sic */ std::map<int, int>         storage<int>;
    template           std::map<int, std::string> storage<std::string>;
    
  • implicitly, by adding the following inside main

    storage<int>.size();
    storage<std::string>.size();
    

in either case taming the linker.

What you attempted is explicit specialization, which does indeed get around the issue although using a different mechanism.

Luc Danton
  • 34,649
  • 6
  • 70
  • 114