2

I am trying to implement a variadic class template with member template functions whose template arguments are independent from the class template parameters, but I have trouble defining the member templates out-of-line.

I simplified my problem down to trying to compile this (sorry can't figure out how to further simplify):

#include <iostream>
#include <string>
#include <typeindex>
#include <typeinfo>
#include <unordered_map>
#include <utility>
#include <vector>

template <class... Types>
class Foo {
public:
  Foo();

  template <class T>
  T& at(const std::string& key);

  template <class T>
  void insert(const std::string& key, const T& value);

private:
  std::tuple<std::unordered_map<std::string, Types>...> sets_;
  std::unordered_map<std::type_index, size_t> type_to_pos_;
};

template<class... Types>
Foo<Types...>::Foo() {
  std::vector<std::type_index> type_indices{std::type_index(typeid(Types))...};
  for (size_t i = 0; i < type_indices.size(); i++) {
    this->type_to_pos_.insert({type_indices[i], i});
  }
}

template<class T, class... Types>
T& Foo<Types...>::at(const std::string& key) {
  std::type_index type_idx{std::type_index(typeid(T))};
  size_t pos;

  pos = this->type_to_pos_.at(type_idx);
  return std::get<pos>(this->sets_).at(key);
}

template <class T, class... Types>
void Foo<Types...>::insert(const std::string& key, const T& value) {
  std::type_index type_idx{std::type_index(typeid(T))};
  size_t pos;

  pos = this->type_to_pos_.at(type_idx);
  std::get<pos>(this->sets_).insert({key, value});
}

int main(int argc, char** argv) {
  Foo<int, float, double> foo{};
  foo.insert("key", 1.0f);
  std::cout << foo.at<float>("key") << std::endl;
  return 0;
}

When trying to compile (C++11), I get the following errors:

$ make
Scanning dependencies of target test
[ 50%] Building CXX object CMakeFiles/test.dir/main.cpp.o
/Users/Jasper/cpp_projects/playground/main.cpp:33:19: error: nested name specifier 'Foo<Types...>::'
      for declaration does not refer into a class, class template or class template partial
      specialization
T& Foo<Types...>::at(const std::string& key) {
   ~~~~~~~~~~~~~~~^
/Users/Jasper/cpp_projects/playground/main.cpp:37:9: error: invalid use of 'this' outside of a
      non-static member function
  pos = this->type_to_pos_.at(type_idx);
        ^
/Users/Jasper/cpp_projects/playground/main.cpp:38:24: error: invalid use of 'this' outside of a
      non-static member function
  return std::get<pos>(this->sets_).at(key);
                       ^
/Users/Jasper/cpp_projects/playground/main.cpp:38:40: error: use of undeclared identifier 'key'
  return std::get<pos>(this->sets_).at(key);
                                       ^
/Users/Jasper/cpp_projects/playground/main.cpp:42:21: error: nested name specifier 'Foo<Types...>::'
      for declaration does not refer into a class, class template or class template partial
      specialization
void Foo<Types...>::insert(const std::string& key, const T& value) {
     ~~~~~~~~~~~~~~~^
/Users/Jasper/cpp_projects/playground/main.cpp:43:19: error: redefinition of 'type_idx'
  std::type_index type_idx{std::type_index(typeid(T))};
                  ^
/Users/Jasper/cpp_projects/playground/main.cpp:34:19: note: previous definition is here
  std::type_index type_idx{std::type_index(typeid(T))};
                  ^
/Users/Jasper/cpp_projects/playground/main.cpp:44:10: error: redefinition of 'pos'
  size_t pos;
         ^
/Users/Jasper/cpp_projects/playground/main.cpp:35:10: note: previous definition is here
  size_t pos;
         ^
/Users/Jasper/cpp_projects/playground/main.cpp:46:9: error: invalid use of 'this' outside of a
      non-static member function
  pos = this->type_to_pos_.at(type_idx);
        ^
8 errors generated.
make[2]: *** [CMakeFiles/test.dir/main.cpp.o] Error 1
make[1]: *** [CMakeFiles/test.dir/all] Error 2
make: *** [all] Error 2

I'm pretty sure that it boils down to the first and fifth errors, but cannot figure out what I am doing wrong. Why does Foo<Types...> not refer to a class template? How can I fix this?

EDIT: added utility library and fixed return value of insert.

P.S. I removed all exception checking for simplicity.

———————- The answer given by @songyuanyao soled the problem, but as @songyuanyao pointed out, get doesnt know pos at compile time so it wont compile. The solution of this helps solving that problem.

Jasper Braun
  • 109
  • 8

1 Answers1

3

You should sperate two sets of template parameters: one for the enclosing class template, and another one for the member function template itself. E.g.

template<class... Types> // for the enclosing class template
template<class T>        // for the member template
T& Foo<Types...>::at(const std::string& key) {
  ...
}

template<class... Types> // for the enclosing class template
template<class T>        // for the member template
void Foo<Types...>::insert(const std::string& key, const T& value) {
  ...
}
songyuanyao
  • 169,198
  • 16
  • 310
  • 405
  • This seems to have fixed the issue. I also had to include the utility library and change that `insert` doesn't return anything. Now I'm running into `no matching function for call to 'get' std::get(this->sets_).insert({key, value});`, which seems unrelated to the original question. – Jasper Braun Jun 16 '20 at 00:45
  • 1
    @JasperBraun `pos` has to be variable known at compile-time, otherwise it can't be specified as template argument. It seems you have to consider the implement of these 2 functions again. – songyuanyao Jun 16 '20 at 00:52