Reputation: 43
I'm currently implementing a dataset helper class template storing floating point values (Scalar
) in a dynamically sized Eigen::Matrix
constructed from a vector of different values types (Element
) additionally storing a reference to this input vector. Now i want to partially specialize the constructor in the vector value type remaining a template in the scalar type to be explicitly instantiated.
Unfortunately i'm getting "unable to match function definition to an existing declaration" on VS 2010. The code is as simple as:
template <class Scalar, class Element> struct DataSet
{
DataSet(std::vector<Element> const & source);
// several generic member functions here ...
Eigen::Matrix<Scalar, ... > data;
std::vector<Element> const & source;
};
template<class Scalar>
DataSet<Scalar, SomeClass>::DataSet(std::vector<SomeClass> const & input)
{
// special impl for Element==SomeClass ...
}
SomeClass should be automatically be figured out by the compiler, when done right but i tried all meaningful combinations but still getting:
*.cpp(79) C2244 : unable to match function definition to an existing declaration
see declaration of 'DataSet<Scalar, Element>::DataSet'
I was not able to find a matching example by searching the internet yet. Thanks in advance!
EDIT:
To make it more specific, in my real world case i want to be able to define several partial specializations to the constructor with different types for Element
e.g:
template<Scalar>
DataSet<Scalar, FirstClass>::DataSet(std::vector<FirstClass> const & first)
: data()
, source(first)
{
// special impl here ...
}
template<Scalar>
DataSet<Scalar, std::shared_ptr<SecondClass> >::DataSet(std::vector<std::shared_ptr<SecondClass> > const & second)
: data()
, source(second)
{
// special impl here ...
}
Redeclaring/specializing the class completely to a certain typename is not desired. Then there is little use to be a template at all. I want the solution as it is, otherwise there might be other strategies to my problem.
FIN:
Since it looks like not being possible to share the type Element
between class template and constructor by only specializing the constructor (which is somehow related to an implicit specialization of the class) i removed the reference source
from the class template entirely and copied the needed information into a generic container and implemented the constructors via overloads.
Upvotes: 3
Views: 504
Reputation: 6791
When defining your constructor, you didn't explicitly provide both template arguments for its class. That would need to be revised as follows:
template<typename T_Scalar, typename T_Element>
DataSet<T_Scalar, T_Element> // template args for type
::DataSet(std::vector<T_Element> const &input) // but none for constructor
{
// stuff
}
Tangentially related: Unlike methods, template arguments for classes cannot be deduced from constructor calls. That is: until C++17 comes around! woo!
The next stumbling block you faced is that template specialisations do not 'inherit' members from their primary template. It is somewhat intuitive to assume they would, but it's just not so. Until I find an official rationale, I presume it's because template arguments might make certain members totally inapplicable to a specialisation, rendering implicit 'inheritance' problematic. If so, it would've been decided to require full redeclaration / not judged worthwhile to add arcane syntax to specify which primary 'base' members are 'inherited'... when you can simply use real inheritance to ensure they are.
Anyway, what that means is that to get a partial specialisation, you need to declare the whole thing - in this case, the class and its constructor - before you can specialise that constructor's definition. You hadn't declared these ahead of time, so the compiler rightly complained that it couldn't see a declaration.
// Define specialised class
template<typename T_Scalar>
class DataSet<T_Scalar, SomeClass>
{
public:
// Declare its ctor
DataSet(std::vector<SomeClass> const &);
}
// Implement its ctor
template<typename T_Scalar>
DataSet<T_Scalar, SomeClass> // complete template args
::DataSet(std::vector<SomeClass> const &input)
{
// stuff
}
To add to your original confusion, which is fair! - note that out-of-line definitions can get very complicated indeed if a template class itself contains a template function, because then you need 2 template
clauses, e.g.
template<typename TA, typename TB>
class Widget {
template<typename TC>
void accept_gadget(TC &&gadget);
};
template<typename TA, typename TB>
template<typename TC>
Widget<TA, TB>
::accept_gadget(TC &&gadget)
{
/* ... */
}
Something that will help a lot in many contexts, especially including such out-of-line template definitions, is if the proposal to allow namespace class
is accepted in a future version. Very sad this didn't make it into C++17... and very odd that it was ever missing in the 1st place!
Upvotes: 3
Reputation: 3849
According to §14.7.3.16:
In an explicit specialization declaration for a member of a class template or a member template that appears in namespace scope, the member template and some of its enclosing class templates may remain unspecialized, except that the declaration shall not explicitly specialize a class member template if its enclosing class templates are not explicitly specialized as well.
Still, you can use std::enable_if to partial-specialize your contructor:
template <class Scalar, class Element> struct DataSet
{
template <class T>
DataSet(std::vector<T> const & input, std::enable_if_t<!std::is_same<T, SomeClass>{}> * = nullptr) {
std::cout << "Element\n";
}
template <class T>
DataSet(std::vector<T> const & input, std::enable_if_t<std::is_same<T, SomeClass>{}> * = nullptr) {
std::cout << "SomeClass\n";
}
};
But this way is restrictive:
Instead, I'd advise you to use a template helper structure:
DataSet(std::vector<Element> const & input) {
Helper<Element>::do_it(input);
}
that you can specialize as you want:
template <class Element>
struct Helper {
static void do_it(std::vector<Element> const & input) {
std::cout << "General form with Element\n";
}
};
template<>
struct Helper<SomeClass> {
static void do_it(std::vector<SomeClass> const & input) {
std::cout << "SomeClass\n";
}
};
template<>
struct Helper<SomeOtherClass> {
static void do_it(std::vector<SomeOtherClass> const & input) {
std::cout << "SomeOtherClass\n";
}
};
...
Upvotes: 2