2

I am looking at the bencode specification and I am writing a C++ port of the deluge bit-torrent client's bencode implementation (written in python). The Python implementation includes a dictionary of { data_types : callback_functions } by which a function wrapper easily selects which encoding function to use by a dictionary look-up of the data type of the variable provided to the wrapper function. I did a search for identifying variable types during run-time of C++ equivalents to python primitives, and found that typeid() might provide what I am looking for. I know that my code will not be storing the results of the typeid() function return value, but I would rather not rely on a function that provides compiler specific string literals. I found a solution that would allow me to provide a portable means to specify the type manually as a string literal, it seems difficult for me to wrap my head around, templates are a nightmare to me.

Here is the Python code in which types are used to select appropriate functions:

encode_func = {}
encode_func[Bencached] = encode_bencached
encode_func[IntType] = encode_int
encode_func[LongType] = encode_int
encode_func[StringType] = encode_string
encode_func[ListType] = encode_list
encode_func[TupleType] = encode_list
encode_func[DictType] = encode_dict
try:
    from types import BooleanType
    encode_func[BooleanType] = encode_bool
except ImportError:
    pass

def bencode(x):
    r = []
    encode_func[type(x)](x, r)
    return ''.join(r)

Here is the C++ sample I found that allows for explicit class type enumeration:

struct Foobar;

template<typename T> struct type_name
{
    static const char* name() { static_assert(false, "You are missing a DECL_TYPE_NAME"); }
};

template<> struct type_name<int> { static const char* name() {return "int";} };
template<> struct type_name<string> { static const char* name() {return "string";} };
template<> struct type_name<Foobar> { static const char* name() {return "Foobar";} };

int main()
{
        cout << type_name<int>::name();  // prints "int"
    cout << type_name<float>::name();  // compile time error. float wasn't declared.
}

If this was implemented appropriately in my classes, I believe I could use switch(variable.name()) to select which function to use to encode the value.

I have three specific questions:

  1. What is the purpose of template<typename T> struct type_name { ... }; in the parent class name() function? I don't really even understand what it is, do templates have scopes? Why can't the name() function just static_assert() a compile error if called?
  2. Why are the template<> struct type_name<type> { static const char* name() {return "type";} } values in the parent-class? Would it not be more clear to simply over-load the name() function for each child-class of a different type?
  3. If I over-load name() for each child-class, and cast a child-class variable to the parent-class, do I still call the name() function call of the child-class if I call variable.name()? or will I cause a compile time error because I called the parent-class's name() function? Does casting change the scope of the class?

I believe that my question stems from a mis-understanding of templates and their purpose in C++, as well as a lack of understanding of inheritance.

Community
  • 1
  • 1

2 Answers2

1

A primer on templates

C++, in contrast to the dynamic Python, is a statically typed language. What this means is that the types of objects are known at compile time (more on the differences here).

A way to generalize code in C++ is templates, which provide the tools for static or compile time polymorphism. To get an idea take a Python example of a min function :

def min(a, b) : 
    if (a < b) : 
        return a
    return b

To translate this in C++ you'd say

int min(int a, int b) {
    return a < b ? a : b;
}

But the problem is that this translation is inacurrate : It only works for integers (and any type that is convertible to integers, which may cause truncation)

One way to overcome this, would be to define overloads for all your types :

double min(double a, double b); // the definition would be the same as above
float  min(float  a, float  b);

But that would be tedious and error prone and far from generic. A better solution is to work with templates and generalize the function for any type T (that supports the < operator):

template<typename T>
T min(T a, T b) { return a < b ? a : b; }

or better yet you could even handle the case when the two arguments have a different type :

template<typename T1, typename T2>
auto min(T1 a, T2 b) -> decltype(a+b) { return a < b ? a : b; }

Templates are a powerful mechanism and I couldn't even begin to provide links and sources; you'd have to google your way through this.

On specific questions

  • The syntax template<> struct<Atype> { ... is called a template (full) specialization. What it does is mandate the layout of a class template for specific template parameters. Here for example :

    template<> struct type_name<int> { static const char* name() {return "int";} };
    

    We are informed that when the type of T in type_name is int the member function name() returns "int"

  • There are no child classes here, you are not building a hierarchy. name is a member function in a class template and you're specializing this class for different types of template parameters.

On the big picture

Being a statically typed language, C++ can provide for cleaner solutions to this :

...a function wrapper easily selects which encoding function to use by a dictionary look-up of the data type of the variable provided to the wrapper function.

You can directly parametrize the behaviour based on your types.

An Example would be to overload your function

void Encode(int arg)       { /* calls encode_int on arg */  }
void Encode(TupleType arg) { /* calls encode_list on arg */ }

Other example would be to work with templates and policies that parametrize the behaviour for each type.

Community
  • 1
  • 1
Nikos Athanasiou
  • 29,616
  • 15
  • 87
  • 153
  • How do I determine if I want to use function over-loading or Template function parametrization? Is there a performance penalty? a code ideology? I am happy using either, I'm just not sure of the trade off here. – user3795241 Jul 02 '14 at 07:55
0

What you're seeing here is not a parent-child relationship as in normal inheritance, it's a specialization. The template defines the default implementation, and the specializations replace that implementation for parameters that match the specific type. So:

  1. template<typename T> struct type_name { ... }; is defining the template class. Without it none of the following definitions would make any sense, since they'd be specializing something that doesn't exist.
  2. Those are the specializations for specific types. They are not child classes.
  3. Because it's not inheritance, you can't cast.

P.S. You can't switch on a string, only integer types.

Mark Ransom
  • 299,747
  • 42
  • 398
  • 622