Reputation: 4205
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.
I have to questions:
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.
#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
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