Home > Software design >  Get "implicit instantiation of undefined template" when trying to combine type traits and
Get "implicit instantiation of undefined template" when trying to combine type traits and

Time:02-17

This is a base Repository class interface, which has a iterator() method. Just like the design pattern URL, the Repository classes and Iterator class are both polymorphism. Instead of virtual function and pimpl, i choose to use CRTP here.

template <typename Impl>
struct RepositoryTraits;

template <typename Impl>
class Repository{
public:
  template<typename ConcreteIterType>
  struct Iterator{
    int next(){
        return static_cast<ConcreteIterType*>(this)->next();
    }
  };
  
  Iterator<typename RepositoryTraits<Impl>::IteratorType> iterator(){
    return static_cast<Impl*>(this)->iterator();
  }
};

Inspired by this wonderful answer. I try to use a trait helper to retrieve the actual IterType From derived Repository class instead of add another template parameter in the Repository's template parameter list:

class RepositoryImpl : public Repository<RepositoryImpl>{
public:
  struct RealIterator : public Iterator<RealIterator>{
      int next(){
          return 1;
      }
  };
  using IteratorType = RealIterator;

  RealIterator iterator(){
      return RealIterator();
  }
};

template <>
struct RepositoryTraits<RepositoryImpl> {
    using IteratorType = RepositoryImpl::IteratorType;
};

Of course, the above code cannot compile. The compiler hint me "implicit instantiation of undefined template 'RepositoryTraits"

I've found this question, but i cannot learn from it. What is the correct way to combine CRTP and type traits here? Or should i use a better design? Any help will be appreciated.


I've found some trait based answers, like http://www.cplusplus.com/forum/general/266019/ or C static polymorphism (CRTP) and using typedefs from derived classes. But I cannot figure out why it can succeed in these case while not in mine.


I've tried to directly return a T and use SFINAE to restrict its type in the base class like below. The code cannot work either, since it force the caller to provide a T for the call of iterator().

Changing iterator to auto deduction return type can work. However, i still want to use SFINAE to fix the Iterator type in subclass. How can this be done?

#include <type_traits>
#include <iostream>

template <typename Impl>
class Repository{
public:
  template<typename ConcreteIterType>
  struct Iterator{
    int next(){
        return static_cast<ConcreteIterType*>(this)->next();
    }
  };
  template <typename T>
  std::enable_if_t<std::is_base_of<Iterator<T>, T>::value, T> iterator(){
    return static_cast<Impl*>(this)->iterator();
  }
};

class RepositoryImpl : public Repository<RepositoryImpl>{
public:
  struct RealIterator : public Iterator<RealIterator>{
      int next(){
          return 1;
      }
  };
  using IteratorType = RealIterator;

  RealIterator iterator(){
      return RealIterator();
  }
};

CodePudding user response:

The traits specialization is not supposed to rely on the definition of the implementation class, but yours does to access RepositoryImpl::IteratorType.

To resolve this, you could move the iterator type into the trait instead. The implementation class can then use the iterator from the traits specialization. Additionally, because your implementation class is not a template, the order of the declarations needs to be fixed:

template <typename Impl>
struct RepositoryTraits;

template <typename Impl>
class Repository {
   public:
    template <typename ConcreteIterType>
    struct Iterator {};

    Iterator<typename RepositoryTraits<Impl>::IteratorType> iterator();
};

class RepositoryImpl;

template <>
struct RepositoryTraits<RepositoryImpl> {
    struct IteratorType
        : public Repository<RepositoryImpl>::Iterator<IteratorType> {};
};

class RepositoryImpl : public Repository<RepositoryImpl> {
   public:
    using IteratorType = RepositoryTraits<RepositoryImpl>::IteratorType;
    Iterator<IteratorType> iterator();
};

Note however that this still has an issue: RepositoryTraits<RepositoryImpl> instantiates Repository<RepositoryImpl>, which requires lookup of RepositoryTraits<Impl>::IteratorType with Impl = RepositoryImpl. However, the point of instantiation of Repository<RepositoryImpl> will be before the definition of RepositoryTraits<RepositoryImpl>, so technically that should fail.

However, there is an open issue with the standard regarding this (CWG 287) and currently compilers seem to follow the suggested fix of the issue, which makes it so the required name lookup can find all members of RepositoryTraits<RepositoryImpl> which were declared before the point at which instantiation of the specialization Repository<RepositoryImpl> is required.

If you want to avoid this issue as well, I suppose it would be easier to move the iterator types into the namespace scope instead of using nested classes.

CodePudding user response:

Another solution target at the whole design, which eliminating the whole Iterator type and use implicit interface dependency like STL way:

#include <type_traits>
#include <iostream>

template <typename Impl>
class Repository{
public:
  using IteratorWrappedType = int;

  auto iterator(){
    return static_cast<Impl*>(this)->iterator();
  }
};

class RepositoryImpl : public Repository<RepositoryImpl>{
public:
  struct RealIterator{
      IteratorWrappedType next(){
          return 2;
      }
  };

  RealIterator iterator(){
      return RealIterator();
  }
};

template <typename T>
int foo(Repository<T>* repo){
    auto it = repo->iterator();
    return it.next();
}

int main(){
    RepositoryImpl repo;
    std::cout << foo(&repo) << std::endl;
}

I posted this to consider this a prompt for an open thread, whether this is a better design or not.

  • Related