Search code examples
c++templatesvariadic-templatessfinae

Error: Variadic template class has incomplete type


I have the code:

#include <unordered_set>

template<typename First, typename Enable = void, typename ... T>
class converged_is_exactly_equal_functor;

template<typename ... T>
bool converged_is_exactly_equal(const T& ...);

template <typename T, typename std::enable_if<std::is_arithmetic<T>::value || std::is_enum<T>::value>::type* = nullptr>
bool is_exactly_equal(const T other, const T one) {
    return (other == one);
}

template<typename First, typename ... T>
class converged_is_exactly_equal_functor<First, std::enable_if<sizeof...(T) == 1>, T ...>
{
    private:
        static std::unordered_set<First*> visited_values;
        void visit_value (const First& value_to_visit) {
            visited_values.insert(&value_to_visit);
        }
        bool is_visited (const First& value_to_check) {
            return (visited_values.find(&value_to_check) != visited_values.end());
        }
    public:
        converged_is_exactly_equal_functor(void){}
        bool operator () (const First& first_arg, const T& ... expanded_args) const {
            if (!is_visited(first_arg)) {
                visit_value(first_arg);
                return is_exactly_equal(first_arg, expanded_args ...);
            }
            return true;
        }
};

template<typename First, typename ... T>
std::unordered_set<First*> converged_is_exactly_equal_functor<First, std::enable_if<sizeof...(T) == 1>, T ...>::visited_values;

template<typename First, typename ... T>
class converged_is_exactly_equal_functor<First, std::enable_if<sizeof...(T) != 1>, T ...>
{
    public:
        converged_is_exactly_equal_functor(void){}
        bool operator () (const First& first_arg, const T& ... expanded_args) const {
            return is_exactly_equal(first_arg, expanded_args ...);
        }
};

template<typename ... T>
bool converged_is_exactly_equal(const T& ... expanded_args) {
    converged_is_exactly_equal_functor<T ... > my_functor;
    return my_functor(expanded_args ...);
}

class a {
    public:
    a() : dbid(1), lsb(123) {}
    int dbid;
    long lsb;
};


bool operator == (const a& other, const a& one) {
    if (&other == &one)
        return true;
    return (
        converged_is_exactly_equal(other.dbid, one.dbid) &&
        converged_is_exactly_equal(other.lsb, one.lsb)
    );
}

int main(void) {
a as, bs;

as == bs;
}

Given that class a is simple group of primitive types, why am I receiving the following error:

my_file.cxx: In instantiation of 'bool converged_is_exactly_equal(const T& ...) [with T = {long int, long int}]':
my_file.cxx:690:56:   required from here
my_file.cxx:682:48: error: 'converged_is_exactly_equal_functor<long int, long int> my_functor' has incomplete type
     converged_is_exactly_equal_functor<T ... > my_functor;

I believe the error has nothing to do with the proprietary data structures, but I don't see why the type could be incomplete. I have the header file for unordered_set included in this same file.

All definitions of is_exactly_equal(T) are done between the forward declarations and the templates' definitions.

Please be as explicit as possible, since I tend to find it complicated to understand template errors in general.

I can provide any more information necessary, but I'll only be back tomorrow. (This one has drained me out :-/)


Solution

  • The problem is in the converged_is_exactly_equal_functor class and in it's use.

    You declare it as follows

    template<typename First, typename Enable = void, typename ... T>
    class converged_is_exactly_equal_functor;
    

    without defining it.

    Then you define two specializations: one for the case sizeof...(T) == 1

    template<typename First, typename ... T>
    class converged_is_exactly_equal_functor<First, std::enable_if<sizeof...(T) == 1>, T ...>
    {
      // ...
    };
    

    and one for the case sizeof...(T) != 1

    template<typename First, typename ... T>
    class converged_is_exactly_equal_functor<First, std::enable_if<sizeof...(T) != 1>, T ...>
     {
       // ...
     };
    

    You use the class inside converged_is_exactly_equal

    template<typename ... T>
    bool converged_is_exactly_equal(const T& ... expanded_args) {
        converged_is_exactly_equal_functor<T ... > my_functor;
        return my_functor(expanded_args ...);
    }
    

    that is called, in your programs, two times

        converged_is_exactly_equal(other.dbid, one.dbid) &&
        converged_is_exactly_equal(other.lsb, one.lsb)
    

    The first time with two int, the second time with two long.

    In both case, you declare a converged_is_exactly_equal_functor value with a second template parameter that isn't void, so doesn't matches the specialization, so matches the main template but the main template is declared but not defined.

    So the error.

    A possible solution: throw away the SFINAE part and simply declare/define the class for a variadic list of following types (the old sizeof...(T) != 0)

    template <typename F, typename ... Ts>
    class converged_is_exactly_equal_functor
     {
       public:
          converged_is_exactly_equal_functor ()
           { }
    
          bool operator () (F const & fa, Ts const & ... ea) const
           { return is_exactly_equal(fa, ea ...); }
     };
    

    and define a specialization for two cases only (the old sizeof...(T) == 1 case)

    template <typename First, typename Second>
    class converged_is_exactly_equal_functor<First, Second>
     {
       private:
          static std::unordered_set<First const *> visited_values;
    
          void visit_value (First const & value_to_visit) const
           { visited_values.insert(&value_to_visit); }
    
          bool is_visited (First const & value_to_check) const
           { return (visited_values.find(&value_to_check) != visited_values.end()); }
    
       public:
          converged_is_exactly_equal_functor ()
           { }
    
          bool operator () (First const & fa, Second const & sa) const
           {
                if ( false == is_visited(fa) )
                 {
                   visit_value(fa);
    
                   return is_exactly_equal(fa, sa);
                 }
    
                return true;
            }
     };
    
    template <typename First, typename Second>
    std::unordered_set<First const *> converged_is_exactly_equal_functor<First, Second>::visited_values;
    

    Observe that I've added some const to let it compile.