Is it possible to make an array of a struct with template type fields?
template<typename T>
struct MyStruct {
T *pField;
};
MyStruct< ?? > mystruct_arr[] = {
{ pFieldOfType1 },
{ pFieldOfType2 },
};
The above obviously doesn't work, but is it possible with other techniques?
I am trying to loop through the array mystruct_arr and call this function on every struct row:
template<typename T>
void SetupField(T &pSourceField, ...)
{
Base *field = ...->findBaseFieldFromDatabase(...);
...
pSourceField = static_cast<T>(field);
...
}
The reason is to try to refactor a very repetitive piece of code where I have to static_cast a long list of different types according to a few different arguments, without making it overly complicated.
A template is not a class, or a struct. It can be thought of as a blueprint, or a recipe, for a class or a struct that gets created when the template gets instantiated.
A template becomes an actual class or a struct only when it is instantiated, by specifying the required template parameters:
MyStruct<int>
Now you have an actual, living, breathing class here. But a MyStruct<int> would be a completely different class than MyStruct<char>. Without specifying the template parameters, MyStruct is not a class, or a struct, or anything that takes up a single byte of RAM. It's just a template for some struct or class.
But with template parameters, such as MySutrct<int>, this becomes an actual class with fields, and perhaps methods. Now that you have a class you can certainly have an array of these, now:
MyStruct<int> mystruct_arr[] = {
};
Or you could have a different array of MyStruct<char>s:
MyStruct<char> mystruct_arr2[] = {
};
But you can't have an array that contains both of these for the same, exact, precise reason that you can't have a single array containing a smorgasbord of different types and classes. You can't have an array that contains both chars, ints, floats, pointers, or various classes. An array always contains values of the same type/class. So pick one particular MyStruct<whatever>, and make an array out of it, that's all you can do.
But you can also declare another structure, now:
struct many_structs {
MyStruct<int> int_struct;
MyStruct<char> char_struct;
// ...
};
This sort of starts to look like an array you would like to have. But it's not an array. It's just an ordinary struct; and instead of using an array index to access a particular template instance, you would refer to the struct member directly.
You could, with some additional work, specialize std::get for your structure, and make this structure look like an array. But now you just realized that you reinvented std::tuple, and can simply to do this:
std::tuple<MyStruct<int>, MyStruct<char>> mystruct_tuple;
Finally, the closest approximation to what you're trying to do is possible only with some additional work, and with a C++ compiler that supports C++17, by declaring an array containing std::anys, or perhaps std::variants. If the array should only contain an a limited enumeration of template instances, std::variant gives the most type-safety, and convenience:
std::variant<MyStruct<int>, MyStruct<char>> mystruct_arr[]={
};
The resulting array contains only these two particular template instances. And with std::any, the screws get loosened further but you'll have to do more work to use and access each value in the array.
You think you want an array of templates.
What you really want is an array of types you can call a particular template function on.
The first is not possible. The second is called type erasure in C++.
template<class T>
using setup_fptr=void(*)(T &, Foo)
using gen_setup_ptr=void(*)(void*, Foo);
template<class T>
setup_ptr<T> get_setup(){ return SetupField<T>; }
template<class T>
gen_setup_ptr get_gen_setup(){
return [](void* p, Foo f){ get_setup<T>( *static_cast<T*>(p), f ); };
}
struct can_setup {
void* ptr=0;
gen_setup_ptr f=0;
can_setup(can_setup const&)=default;
can_setup& operator=(can_setup const&)=default;
can_setup()=default;
explicit operator bool() const{return f;}
template<class T>
can_setup(T* pt):
ptr(pt),
f( get_gen_setup<T>() )
{}
void setup( Foo foo ) const {
f(ptr, foo );
}
};
store an array of can_setup. Loop over them calling .setup(foo).
Foo is a placeholder for whatever other args you are using.
This technique is known as type erasure; we forget (erase) everything about T except that we can setup it.
Related
I have a template function that I want to store a pointer to inside a std::vector.
The function looks like this:
template<typename T> void funcName(T& aT, std::vector<std::string>& fileName){...}
Now I want to store multiple pointers to functions of this kind inside a std::vector. For non-template functions I would do it like this:
typedef std::vector<std::string> string_vt;
typedef void func_t(T&, string_vt&);
typedef func_t* funcPointer;
typedef std::vector<funcPointer> funcPointer_vt;
But what is the correct syntax for template functions? How can I store them?
EDIT: First of all, thank you for your fast response. This was my first Question on Stack Overflow, so I am sorry for not providing enough information.
The set of T is finite, it can either be of type ClassA or type classB. In these function templates I want to do changes to T (so either ClassA or ClassB) with some hard coded data. I have 8 of these functions, which basically initiate a default constructed T with data specific to the function. In my program, I want to initiate 2*8 default constructed T's (8 ClassA and 8 ClassB). Therefore I run a for loop, calling one function after the other, to initiate my T objects with the function's body data.
for(int i = 0; i < initT.size(); ++i){
init_T[i]<T>(someT, fileName);
}
The for loop has as much iterations as there are function pointers inside the vector. At every iteration the function is called with some previously default constructed T and some other parameter. At the end the goal is to have 8 initiated T's with data specific to the function.
EDIT2: In case it helps, here is some actual source code. Inside the following function template I want to access my vector of function pointers in order to call the respective function.
template<typename T_Relation, typename T_Relation_Vec, bool row>
void bulk_load(initRelation_vt& aInitFunctions, T_Relation_Vec& aRel_Vec, const bool aMeasure, const uint aRuns, const char* aPath)
{
for(size_t i = 0; i < aRuns; ++i)
{
MemoryManager::freeAll();
aRel_Vec.clear();
string_vt fileNames;
for(size_t j = 0; j < aInitFunctions.size(); ++j)
{
aRel_Vec.emplace_back(T_Relation());
aInitFunctions[j]<T_Relation>(aRel_Vec[j], fileNames);
BulkLoader bl(fileNames[j].c_str(), tuples, aRel_Vec[j], delimiter, seperator);
Measure lMeasure;
if(aMeasure)
{
lMeasure.start();
}
try
{
bl.bulk_load();
if(row)
{
BulkInsertSP bi;
bi.bulk_insert(bl, aRel_Vec[j]);
}
else
{
BulkInsertPAX bi;
bi.bulk_insert(bl, aRel_Vec[j]);
}
}
catch(std::exception& ex)
{
std::cerr << "ERROR: " << ex.what() << std::endl;
}
lMeasure.stop();
if(aMeasure)
{
std::ofstream file;
file.open (aPath, std::ios::out | std::ios::app);
//print_result(file, flag, lMeasure.mTotalTime());
file.close();
}
}
}
}
This line is where the vector of function template pointers is accessed.
aInitFunctions[j]<T_Relation>(aRel_Vec[j], fileNames);
Templates are an advanced technique for static polymorphism. In a typed language, like C++, without static polymorphism you would have to separately define every entity used and precisely indicate every entity referred to.
Mechanisms of static polymorphism in C++ allow to automate indication of function or method and defer it until build via overloading. It allows you to define multiple entities sharing some characteristics at once via templates and defer definition of particular specializations until build, inferred from use.
(Notice that in various scenarios, static polymorphism allows separate code, so that changes to use and to definition are independent, which is very useful.)
The important implication of this mechanism is that every specialization of your template may be of different type. It is unclear, as of when I'm responding, whether you want to store pointers to a single or multiple types of specialization in one type of container. The possibilities depend also on parameter and result types of the function template.
A function in C++ has a type that is a combination of list of its parameter types and its return type. In other words, two functions that take and return the same types are of the same type. If your function template neither took or returned template parameter type (ie. T) nor templated type (eg. std::vector<T>), every specialization of this function template would be taking and returning the same types and would therefore be a function of the same type.
template <typename T>
int func() { ... }
This (arguably useless) function template takes no arguments and returns int, whatever T is used to specialize the template. Therefore a pointer to it could be used wherever the parameter is defined as int (*f)(). In this case you could keep pointer to any specialization in one vector.
typedef std::vector<std::string> string_vt;
typedef int func_t();
typedef func_t* funcPointer;
typedef std::vector<funcPointer> funcPointer_vt;
funcPointer x = &func<int>;
funcPointer y = &func<float>;
As can be seen, every specialization of your function template is of the same type and both pointers fit in the same container.
Next case - what if function header depends on a template parameter? Every specialization would have a different signature, that is a different function type. The pointers to all of them would be of different types - so it wouldn't be possible to even typedef this pointer once.
template <typename T>
void func(std::vector<T> param) { ... }
In this case function template specialization is of different type depending on T used to specialize.
typedef int func_t_int(std::vector<int>);
typedef func_t_int* funcPointerInt;
typedef std::vector<funcPointerInt> funcPointerInt_vt;
typedef float func_t_float(std::vector<float>);
typedef func_t_float* funcPointerFloat;
typedef std::vector<funcPointerFloat> funcPointerFloat_vt;
funcPointerInt x = &func<int>;
funcPointerFloat x = &func<float>;
Specializations are of different types, because they take different type of vectors. Pointers do not fit in the same container.
It's mention-worthy at this point, that in this case it's not necessary to define every pointer type separately. They could be a template type:
template <typename T>
using funcPointer = void (*)(std::vector<T>);
Which now allows funcPointer<int> to be used as a type qualifier, in place of earlier funcPointerInt.
funcPointer<float> y = &func<float>;
In more complicated situations a template could be created, whose every specialization is of a different type, and then would use a single instance of concrete vector to store various pointers to functions of type of only one of the specializations of your template. Although a simple template like in the example can only produce a single function per type, because every specialization yields one type of function and one function of that type, it's not impossible to conceive a scenario where various pointers to functions are obtained, both to specializations and usual functions, perhaps from various sources. So the technique could be useful.
But yet another scenario is that despite every specialization of the template being of different type, there's a need to store pointers to various specializations in single std::vector. In this case dynamic polymorphism will be helpful. To store values of different types, fe. pointers to functions of different types, in one type of variable, requires inheritance. It is possible to store any subclass in a field defined as superclass. Note however, that this is unlikely to accomplish anything really and probably not what you're really looking for.
I see two general possibilities now. Either use a class template with a method, which inherits from a non-template class.
template <typename T>
class MyClass : BaseClass
{
public:
T operator()(const T& param, int value);
}
MyClass<int> a;
MyClass<float> b;
BaseClass* ptr = &a;
ptr = &b;
While every specialization of this class may be of a different type, they all share superclass BaseClass, so a pointer to a BaseClass can actually point to any of them, and a std::vector<funcPointerBase> can be used to store them. By overloading operator() we have create an object that mimics a function. The interesting property of such a class is that it can have multiple instances created with parameter constructors. So effectively class template produces specializations of multiple types, and in turn every specialized class can produce instances of varying parametrization.
template <typename T>
class MyClass : BaseClass
{
int functor_param;
public:
MyClass(int functor_param);
T operator()(const T& param, int value);
}
This version allows creation of instances that work differently:
MyClass<int> a(1);
MyClass<int> b(2);
MyClass<float> c(4);
MyClass<int>* ptr = &a;
ptr = &b;
ptr = &c;
I am no expert on functors, just wanted to present the general idea. If it seems interesting, I suggest researching it now.
But technically we're not storing function pointers, just regular object pointers. Well, as stated before, we need inheritance to use one type of variable to store values of various types. So if we're not using inheritance to exchange our procedural functions for something dynamically polymorphic, we must do the same to pointers.
template <typename T>
T func(std::pair < T, char>) {}
template <typename T>
using funcPointer = T(*)(std::pair<T, char>);
template <typename T>
class MyPointer : BasePointer
{
funcPointer<T> ptr;
public:
MyPointer(funcPointer<T> ptr);
T()(std::pair <T, char>) operator*(std::pair <T, char> pair)
{
*ptr(pair);
}
};
This, again, allows creation of single std::vector<BasePointer> to store all possible pseudo-function-pointers.
Now the very important bit. How would You go about calling those, in either scenario? Since in both cases they are stored in a single std::vector<>, they are treated as if they were of the base type. A specific function call needs parameters of specific type and returns a specific type. If there was anything that all subclasses can do in the same way, it could be exposed by defining such a method in base class (in either scenario using functors or pointer..ors?), but a specific specialized function call is not that kind of thing. Every function call that You would want to perform in the end, after all this struggle, would be of a different type, requiring different type of parameters and/or returning different type of value. So they could never all fit into the same place in usual, not templated code, the same circumstances in execution. If they did, then dynamic polymorphism wouldn't be necessary to solve this problem in the first place.
One thing that could be done - which is greatly discouraged and probably defeats the purpose of dynamic polymorphism - is to detect subclass type at runtime and proceed accordingly. Research that, if you're convinced you have a good case for using this. Most likely though, it's probably a big anti-pattern.
But technically, anything you may want to do is possible somehow.
If I have correctly understood you, I may have a really simple and efficient solution:
template<class...Ts>
struct functor{
//something like a dynamic vtable
std::tuple<void(*)(Ts&,std::vector<std::string>&)...> instantiated_func_ptr;
template<class T>
void operator ()(T& aT,std::vector<std::string>& fileName){
get<void(*)(T&,std::vector<std::string>&)>(instantiated_func_ptr)
(aT,fileName);
}
};
VoilĂ !!
Until c++17, get<typename> is not defined so we have to define it (before the definition of the template functor above):
template<class T,class...Ts>
struct find_type{
//always fail if instantiated
static_assert(sizeof...(Ts)==0,"type not found");
};
template<class T,class U,class...Ts>
struct find_type<T,U,Ts...>:std::integral_constant<size_t,
find_type<T,Ts...>::value+1>{};
template<class T,class...Ts>
struct find_type<T,T,Ts...>:std::integral_constant<size_t,0>{};
template<class T,class...Ts>
constexpr decltype(auto) get(const std::tuple<Ts...>& t){
return get<find_type<T,Ts...>::value>(t);
}
And an example to show how to use it:
struct A{
void show() const{
std::cout << "A" << "\n";
}
};
struct B{
void show() const{
std::cout << "B" << "\n";
}
};
template<class T>
void func1(T& aT,std::vector<std::string>& fileName){
std::cout << "func1: ";
aT.show();
}
template<class T>
void func2(T& aT,std::vector<std::string>& fileName){
std::cout << "func2: ";
aT.show();
}
template<class T>
void func3(T& aT,std::vector<std::string>& fileName){
std::cout << "func3: ";
aT.show();
}
using functorAB = functor<A,B>;
int main(){
auto functor1=functorAB{{func1,func1}};//equivalent to functorAB{{func1<A>,func1<B>}}
auto functor2=functorAB{{func2,func2}};
auto functor3=functorAB{{func3,func3}};
auto v=std::vector<functorAB>{functor1,functor2,functor3};
auto a=A{};
auto b=B{};
auto fileNames = std::vector<std::string>{"file1","file2"};
for(auto& tf:v)
tf(a,fileNames);
for(auto& tf:v)
tf(b,fileNames);
}
In practice it is just a reproduction of the virtual call mechanism,
the tuple in functor is kind of virtual table. This code is not
more efficient than if you had written an abstract functor with virtual
operator() for each of your class A and B and then implemented it for each of
your functions... but it is much more concise, easier to maintain and may produce less binary code.
I'm using template functions for object construction to create objects from reflection data, and it works pretty well, but now I want to support STL container types in the reflection system so that objects such as:
// Note - test case only
// for real world usage it would probably not be structured like this
// and the phrases would be mapped by an id or something
struct Phrases {
std::vector<std::string> phrases;
};
typedef std::string Lang;
struct Langs {
std::map< Lang, Phrases > translations;
};
Can be supported. I can do some regex magic on the return of
typeid( object ).name()
to figure out if an object is a vector or a map, and what the parameter arguments for the object is. And I have tried some template magic to do it something like the following, where CreateString, ConstructString & DestroyString are stand in functions and the data is stand in as well for something a bit more complex that uses a type database to handle object construction.
// Representational of code, basically a copy-paste to a different test project where I can work out the problems with this specific vector problem
// Vector specialised construction
template <typename T> void ConstructVector( void* object, const std::vector<std::string>& data ) {
T* vec = (T*)object;
Name vector_type = GetVectorTypeName<T>();
void *obj;
CreateString(&obj);
// All fields in this type should be valid objects for this vector
for( std::vector<std::string>::const_iterator it = data.begin(), end = data.end(); it != end; ++it ) {
// Push it
vec->push_back(*obj);
// Get address to new instance
void *newly = &vec->back();
ConstructString(newly,*it);
}
DestroyString(&obj);
}
Which doesn't work owing to the illegal indirection with "vec->push_back(*obj);" which I can't case because I don't actually know the type. Basically what I need to be able to do is create this vector with some blank unset elements already in it, or add new elements to it without actually having the type, because if I can get a pointer to a memory block inside the vector I can roll with that and construct the object. But the vector add requirements such as
vector::push_back( T& value )
or
vector::insert( Iter&, T& )
Won't work for me unless I can get my hands on that T type from inside the template
pastebin of testing code to try and solve this:
http://pastebin.com/1ZAw1VXg
So my question is, how can I get the std::string part of a std::vector declaration when I'm inside a template like
template <typename T> void SomeFunc() {
// Need to get std::string here somehow
// Alternatively need to make the vector a certain size and then
// get pointers to it's members so I can construct them
}
SomeFunc<std::vector<std::string>>>();
There are two ways to accomplish this.
1) Either you make use of the fact that std::vector<> (like all standard library container classes) maintains a member type value_type, which represents the type of the elements stored in the vector. So you can do this:
template <typename T> void SomeFunc() {
typename T::value_type s; // <--- declares a `std::string` object
// if `T` is a `std::vector<std::string>`
}
2) Or else, you change the declaration of your function and make use of template template parameters:
template <template <typename> class T, typename Elem>
void SomeFunc(T<Elem> &arg)
{
Elem s;
}
However, there is a small problem with that: std::vector is really a template with two parameters (element type and allocator type), which makes it a little difficult to use the template template parameters and still keep the syntax simple. One thing that worked for me is to declare an alias of the vector type that leaves only one template parameter:
template <typename Elem>
using myvector = std::vector<Elem>;
Then I can use SomeFunc like this:
int main()
{
myvec<std::string> vec;
SomeFunc(vec);
}
In c++11, you can use decltype and std::decay to that effect:
std::vector<int> vec;
using T = typename std::decay<decltype(*vec.begin())>::type;
Is there a way to implement a compile-time type dictionary via C++ templates?
e.g. if I have a number of classes like these:
class ProtocolMajor1Minor2 { ... };
class ProtocolMajor4Minor3 { ... };
...
class ProtocolMajor12Minor21 { ... };
...is there a way to use C++ templates that would allow me to do something like this:
void foo(int majorVersion, int minorVersion)
{
LookupMap<majorVersion,minorVersion>::innertype *specific =
new LookupMap<majorVersion,minorVersion>::innertype;
return specific->FunctionalityFoo();
}
In case it is not clear, LookupMap acts just as the name says: given the two integer parameters (protocol major and minor versions) it is supposed to provide the specific protocol type I need, via the innerType "trait".
I can't use the preprocessor to create function "foo" as a macro (using ## or #), for two reasons: (a) it is big, not like in this example, and I don't want a huge function coded as a macro, and (b) the naming mappings are not direct (i.e. major version A and minor version B do not point to class "ProtocolMajorAMinorB".
You may also be thinking that "FunctionalityFoo" should be a member of a base type:
you are correct, but this is code generated from legacy code generators, i.e. untouchable.
There are in fact many functions like "FunctionalityFoo" generated for each combination of (major,minor), and I don't want to create if/then/else ladders for each of them.
I tried template specialization but failed to find a syntax that works.
Is there a way to do it via templates?
No, templates are compile-time construct, so you cannot use variables as template arguments.
For this to work you need to do something like this (i.e. not use runtime-provided variables):
template <int A, int B>
struct LookupMap { };
template <>
struct LookupMap<1, 2> { typedef Type1_2 innertype; };
// ...
template <int A, int B>
sometype foo() {
typedef typename LookupMap<A, B>::innertype T;
T* ptr = new T; // also, this leaks, use a smart pointer or something
return ptr->something();
}
// when used
foo<1, 2>();
What is the use of c++ non-type template values? What can be done with this:
template <int I>
class MyClass
{
public:
MyClass();
.
. //Use I;
.
}
that can't be done with this:
class MyClass
{
int I;
public:
MyClass(int i) : I(i) {}
.
. //Use I
.
}
The template version seems to me to create unnecessary overhead of the compiler creating two separate types of objects, with separate copies of every class method.
One use is that template argument deduction can be used to work out I, saving the programmer the bother:
template<typename T, size_t N>
T *end(T (&ra)[N]) {
return ra + N;
}
int main() {
std::string headings[] = {"name", "dob", "address"};
std::ostream_iterator<std::string> output(std::cout, "\t")
std::copy(headings, end(headings), output);
// or
std::vector<std::string> headingvec(headings, end(headings));
}
No messing with sizeof every time you want to use an array.
I'm pretty sure that the initial motivation for it was for class templates like std::bitset, though, as others have mentioned.
With template you can do this:
template <int I>
class MyClass
{
int array[I];
}
the creation of separate types is actual a useful idom (google "int to type idiom "). But beyond that, the template version lets the compile know the value of the number at compile time, not runtime. Which means that there is a different set of possible optimizations that are available. Really the possibilities are tremendous, this feature basically makes c++ templates a full computing language in itself.
For a basic example of common usage, suppose you wanted to make a container that had a fixed size known at compile time. You couldn't implement that without non-type template params. Something like this:
array<int, 10> x; // create an object 10 int's big
The amount of things such a construct can be used are endless. One basic example would be boost::array, which specifies how large it will be through a non-type template parameter. It would not be possible to do the same in any other way (aggregate type, contents on stack).
The difference is that the template version gets expanded at compile time, so the integer compiles away to an implicit constant; in the non-templated version, the integer still exists at run time. In the templated version, MyClass<1> and MyClass<2> are two different, incompatible types, and trying to assign one to another will produce a compiler error.
A typical example for this is a generic vector class (the mathematical vector, not std::vector) where most methods work the same regardless of the vector's dimension (adding 2-space vectors and 4-space vectors is exactly the same operation), but some are only defined for special cases (cross product is only defined for 3-space and 7-space vectors). If you were to store the vector's dimension in a member variable, you'd have to do a runtime check each time an operation needs to be performed with possibly incompatible arguments (e.g. adding a 2-space vector to a 4-space vector), and you'd have to handle the resulting error at run-time.
With non type template parameters you really can do a lot of things. One such example, usually related to template meta-programming ( see Andrei Alexandrescu's book on this: "Modern C++ Design" ) is a template class that computes the factorial at compile time. Something like
template<int N>
class factorial
{
public:
enum { value = N * factorial<N-1>::value };
}
Then you can fully specialize your factorial class for 0, so it actually ends somewhere:
template<>
class factorial<0>
{
public:
enum { value = 1 };
}
Then, you can use this class to compute some factorial at compile-time like this:
int f4 = factorial<4>::value; // f4 will be 24 at compile time. Neat!
This may not be the most useful example, but you get the picture.
You can find this example on Wikipedia, where you can also read more on the subject.
Cheers.
One use is metaprogramming.
An example (stolen shamelessly from wikipedia) is a template that computes the power of a number at compile-time:
template <int N>
struct Factorial
{
enum { value = N * Factorial<N - 1>::value };
};
template <>
struct Factorial<0>
{
enum { value = 1 };
};
// Factorial<4>::value == 24
// Factorial<0>::value == 1
void foo()
{
int x = Factorial<4>::value; // == 24
int y = Factorial<0>::value; // == 1
}
it's useful if you want to define "constants" for types...
for example..
template <typename _foo, int _id>
class field
{
public:
int id() { return _id; }
};
now when you declare an instance of field, you can encode it's id...
field<int, 10> _f1;
field<double, 11> _f2;
etc. why is this useful? consider protocols employed by third-parties, such as exchanges (say FAST/FIX etc.)
Illustration by example.
I am, at the moment, working on a Trie structure (by the way if anyone knows a good implementation...).
A Trie is basically a N-ary tree, with the first level being mostly complete and then getting sparser and sparser the deeper you delve in (this is not a propriety of the structure, it's just that most dictionaries have much more short words than longer ones).
I was thinking of using this feat of templates to minimize the number of nodes allocated. In a non templated way it would go:
class Node3; // contains N Node2
class Node2; // contains N Node1
class Node1; // contains N Node0
class Node0; // contains N Node
class Node; // contains N POINTERS to Node
With the template I do:
template <size_t L> class Node; // contains N Node<L-1>
template <> class Node<0>; // contains N Node
class Node; // contains N POINTERS to Node
And saves myself the boring task of writing a macro or copying the code over and over.
I have a dilemma. Suppose I have a template class:
template <typename ValueT>
class Array
{
public:
typedef ValueT ValueType;
ValueType& GetValue()
{
...
}
};
Now I want to define a function that receives a reference to the class and calls the function GetValue(). I usually consider the following two ways:
Method 1:
template <typename ValueType>
void DoGetValue(Array<ValueType>& arr)
{
ValueType value = arr.GetValue();
...
}
Method 2:
template <typename ArrayType>
void DoGetValue(ArrayType& arr)
{
typename ArrayType::ValueType value = arr.GetValue();
...
}
There is almost no difference between the two methods. Even calling both functions will look exactly the same:
int main()
{
Array<int> arr;
DoGetValue(arr);
}
Now, which of the two is the best? I can think of some cons and pros:
Method 1 pros:
The parameter is a real class not a template, so it is easier for the user to understand the interface - it is very explicit that the parameter has to be Array. In method 2 you can guess it only from the name. We use ValueType in the function so it is more clear this way than when it is hidden inside Array and must be accessed using the scope operator.
In addition the typename keyword might be confusing for many non template savvy programmers.
Method 2 pros:
This function is more "true" to its purpose. When I think if it, I don't really need the class to be Array. What I really need is a class that has a method GetValue and a type ValueType. That's all. That is, this method is more generic.
This method is also less dependent on the changes in Array class. What if the template parameters of Array are changed? Why should it affect DoGetValue? It doesn't really care how Array is defined.
Evey time I have this situation I'm not sure what to choose. What is your choice?
The second one is better. In your "pros" for the first one, you say, "it is very explicit that the parameter has to be Array". But saying that the parameter has to be an Array is an unnecessary limitation. In the second example, any class with a suitable GetValue function will do. Since it's an unnecessary limitation, it's better to remove it (second one) than to make it explicit (first one). You'll write more flexible templates, which is useful in future when you want to get a value from something that isn't an Array.
If your function is very specific to ArrayType, and no other template will satisfy its interface requirements, use #1 as it's both shorter and more specific: the casual reader is informed that it operates on an ArrayType.
If there's a possibility that other templates will be compatible with DoGetValue, use #2 as it's more generic.
But no use obsessing, since it's easy enough to convert between them.
My friend proposed two more, somewhat more extreme, methods:
Method 3: gives you the ability of using types that don't have a ::ValueType.
template <typename ArrayType, typename ValueType = ArrayType::ValueType>
void DoGetValue(ArrayType& arr)
{
ValueType value = arr.GetValue();
...
}
Method 4: a cool way of forcing the array to be a class that has one template parameter.
template <template <typename> class ArrayType, typename ValueType>
void DoGetValue(ArrayType<ValueType>& arr)
{
ValueType value = arr.GetValue();
...
}