Lemming
Lemming

Reputation: 4205

Return type dependent on template parameter

Intro

I want to represent sampled functions (large vectors basically) that can have scalar, or vector values. (scalar to some extent being a special case of a 1 element vector)

I want to have an interface that allows writing stuff like g[i] = 2*f[i] + f[j]. Hence, for a vector valued function f[i] needs to return an object which can do arithmetic and assignment on the vector elements. However, in the scalar case this system should be as efficient as a simple vector. So, ideally f[i] should return something that is equivalent to e.g. a reference to a double (performance wise).

My approach so far is to have a class Function with a template parameter extent which can be either One, or Large.

template<typename T, Extent extent>  // extent is either `One`, or `Large`
class Function;

It defines an operator[] which returns either a reference to the data type (if extent is One), or a View object (if extent is Large), that behaves like a vector.

// If `extent` is `One`:  `Accessor_` is a `T&`.
// If `extent` is `Large`:  `Accessor_` is a `View`.
Accessor_ operator[](int i) {
    return make_accessor<T, extent>(data_[stride_*i]);
}

The function make_accessor is a template function with two specializations.

/// Convinience function to create an accessor.
template<typename T, Extent extent>
Accessor<T, extent> &make_accessor(T &data);
template<typename T>
Accessor<T, Extent::One> &make_accessor(T &data) { return data; }
template<typename T>
Accessor<T, Extent::Large> &make_accessor(T &data) { return View<T>(&data); }

Unfortunately, this doesn't work. Technically, it compiles. However, I get a linker error saying that make_accessor is not defined.

g++ -Wall -Wextra -o elem_type elem_type.cc -std=c++11
/tmp/ccz3zgwS.o: In function `Function<double, (Extent)0>::operator[](int)':
elem_type.cc:(.text._ZN8FunctionIdL6Extent0EEixEi[_ZN8FunctionIdL6Extent0EEixEi]+0x30):
undefined reference to `(anonymous namespace)::Accessor_<double, View<double>,
(Extent)0>::Type& make_accessor<double, (Extent)0>(double&)'
/tmp/ccz3zgwS.o: In function `Function<double, (Extent)1>::operator[](int)':
elem_type.cc:(.text._ZN8FunctionIdL6Extent1EEixEi[_ZN8FunctionIdL6Extent1EEixEi]+0x30):
undefined reference to `(anonymous namespace)::Accessor_<double, View<double>,
(Extent)1>::Type& make_accessor<double, (Extent)1>(double&)'
collect2: error: ld returned 1 exit status
make: *** [elem_type] Error 1

Googling this error doesn't give me any hints. All I can find are questions where people put the implementation into a separate .cpp file. However, this is not the case here. Everything is in one single .cpp file. So, I don't understand how an undefined reference is possible. I tried both, GCC 4.8.1, and clang 3.3. The linker error appears in both cases (GNU ld 2.23.2). I also tried explicitely template instantiating make_accessor it didn't help.

My Questions

I have to questions:

  1. What causes this linker error, and how can I fix it?
  2. Do you think there is an easier way of achieving what I described above?

EDIT Question 1 is solved. (See below). However, the code has a whole lot more issues. I'll come back to 2 in a new question once everything is fixed.

The Full Code

#include <cassert>
#include <stdexcept>
#include <vector>
#include <iostream>


/// The extent of elements of a function.
enum class Extent { One, Large };

/// Convert a precise size to an extent.
constexpr Extent extentof(int size) {
    return size == 1 ? Extent::One :
        size > 1 ? Extent::Large :
        throw std::logic_error("Invalid size");
}


/// View into a part of an array.
template<typename T>
class View {
public:
    View() = default;
    View(T *data, int size) : data_(data), size_(size) {
        assert(data_ != nullptr);
    }

    int size() const { return size_; }
    T &operator[](int i) {
        assert(data_ != nullptr);
        assert(i < size_);
        return data_[i];
    }

private:
    T *data_;
    int size_;
};


namespace {
template<typename T, class View, Extent extent>
struct Accessor_;
template<typename T, class View>
struct Accessor_<T, View, Extent::One> {
    typedef T Type;
};
template<typename T, class View>
struct Accessor_<T, View, Extent::Large> {
    typedef View Type;
};
}

/// The type to access a single element.
template<typename T, Extent extent>
using Accessor = typename Accessor_<T, View<T>, extent>::Type;

/// Convinience function to create an accessor.
template<typename T, Extent extent>
Accessor<T, extent> &make_accessor(T &data);
template<typename T>
Accessor<T, Extent::One> &make_accessor(T &data) { return data; }
template<typename T>
Accessor<T, Extent::Large> &make_accessor(T &data) { return View<T>(&data); }


/// A function whos values have an extent.
template<typename T, Extent extent>
class Function {
    typedef Accessor<T, extent> Accessor_;
public:
    Function() = default;
    explicit Function(int size, int elem_size=1)
        : data_(size*elem_size), size_(size), stride_(elem_size) {
        assert(extent == extentof(elem_size));
    }

    Accessor_ &operator[](int i) {
        return make_accessor<T, extent>(data_[stride_*i]);
    }

private:
    typedef std::vector<T> Store;
    Store data_;
    int size_;
    int stride_;
};


int main() {
    Function<double, Extent::One> func1(3);
    Function<double, Extent::Large> func2(3, 2);

    func1[1] = 1;  // Should change the value stored inside `func1`.
    func2[0][1] = 2;  // Should change the value stored inside `func2`.
}

Upvotes: 0

Views: 1825

Answers (1)

Sebastian Redl
Sebastian Redl

Reputation: 72215

You can't partially specialize function templates. What you're doing is adding additional overloads that you're not calling. The linker error is pointing out that the compiler tried to use the first templates (those with two parameters), but there aren't any definitions for them.

Upvotes: 1

Related Questions