views:

502

answers:

3

Consider the following code:

template <int dim>
struct vec
{
    vec normalize();
};


template <>
struct vec<3>
{
    vec cross_product(const vec& second);
    vec normalize();
};

template <int dim>
vec<dim> vec<dim>::normalize()
{
    // code to normalize vector here
    return *this;
}

int main()
{
    vec<3> direction;
    direction.normalize();
}

Compiling this code produces the following error:

1>main.obj : error LNK2019: unresolved external symbol "public: struct vec<3> __thiscall vec<3>::normalize(void)" (?normalize@?$vec@$02@@QAE?AU1@XZ) referenced in function _main

+2  A: 

You haven't supplied a definition of vec<3>::normalize, so the linker obviously can't link to it.

The entire point in a template specialization is that you can supply specialized versions of each method. Except you don't actually do that in this case.

jalf
+1  A: 

You can't as far as I know call the "generic" version.

Alternatively, you can define your generic implementations outside of the classes as functions:

template <int dim>
struct vec
{
};

namespace impl {
    template <int dim>
    vec<dim> normalize(const vec<dim>& v)
    {
        // code to normalize vector here
        return v;
    }
}

template <>
struct vec<3>
{
    vec cross_product(const vec& second);
    vec normalize() { return impl::normalize(*this); }
};


int main()
{
    vec<3> direction;
    direction.normalize();
}
Frank Krueger
+7  A: 

You can't :) What you want is to specialize the member functions instead:

template <int dim>
struct vec
{
    // leave the function undefined for everything except dim==3
    vec cross_product(const vec& second);
    vec normalize();
};

template<>
vec<3> vec<3>::cross_product(const vec& second) {
    // ...
}

template <int dim>
vec<dim> vec<dim>::normalize()
{
    // code to normalize vector here
    return *this;
}

Another, slightly more complicated solution is to use boost::enable_if:

template <int dim>
struct vec
{
    // function can't be called for dim != 3. Error at compile-time
    template<int dim1>
    typename boost::enable_if_c< dim == dim1 && dim1 == 3, vec<dim1> >::type 
    cross_product(const vec<dim1>& second) {
        // ...
    }
    vec normalize();

    // delegate to the template version
    void without_params() {
        // delegate
        this->without_params<dim>();
    }

private:
    // function can't be called for dim != 3. Error at compile-time
    template<int dim1>
    typename boost::enable_if_c< dim == dim1 && dim1 == 3 >::type 
    without_params() {
        // ...
    }   
};

template <int dim>
vec<dim> vec<dim>::normalize()
{
    // code to normalize vector here
    return *this;
}

That will cause a compile time error if cross_product is called for any dim != 3. Note that that 'trick' only works for functions with parameters, since only then the template parameter can be auto-deduced. For cases without parameters, i have provided a function without_parameters above :).

Johannes Schaub - litb
I really like this solution, no redundant code conceptually identical functions. I now realize I was using class specialization incorrectly.
da_code_monkey
i messed up a bit yesterday with the template param names. now it also contains a version for functions without parameters. enjoy :)
Johannes Schaub - litb
Also will enable_if work for member variables as well?
da_code_monkey
nope. it's just a mechanism to exclude a template from overload resolution if the substitution of a parameter violates c++ rules or accesses nonexisting types. lookout for "SFINAE".
Johannes Schaub - litb
you can derive your template from a template that is itself specialized to provide different members for different parameters. but if you find that you need to specialize members variables, then consider whether it's better to go and specialize the whole template itself.
Johannes Schaub - litb
da_code_monkey, you can get the definition of enable_if_c here if you are interested in its implementation: http://stackoverflow.com/questions/257288/possible-for-c-template-to-check-for-a-functions-existence#264088 (the enable_if there works like boost::enable_if_c)
Johannes Schaub - litb