Automatic Return Type (C++98)

Contents[Show]

Depending on the used C++ standard, there are different ways to return the right return type of a function template. In this post, I start with traits (C++98), continue in my next post with C++11/14, and end with concepts (C++20).

 AutomaticReturnType

Here is the challenge for today's post.

template <typename T, typename T2>
??? sum(T t, T2 t2) {
    return t + t2;
}

 

When you have a function template such as sum with at least two type parameters, you can not decide in general the return type of the function. Of course, sum should return the type the arithmetic operation t + t2 provides. Here are a few examples using run-time type information (RTTI) with std::type_info.

// typeinfo.cpp

#include <iostream>
#include <typeinfo>
 
int main() {
	
    std::cout << '\n';
 
    std::cout << "typeid(5.5 + 5.5).name(): " << typeid(5.5 + 5.5).name() << '\n';
    std::cout << "typeid(5.5 + true).name(): " << typeid(5.5 + true).name() << '\n';
    std::cout << "typeid(true + 5.5).name(): " << typeid(true + 5.5).name() << '\n';
    std::cout << "typeid(true + false).name(): " << typeid(true + false).name() << '\n';

    std::cout << '\n';
    
}

 

I executed the program on Windows using MSVC, because MSVC produces in contrast to GCC or Clang human-readable names.

 typeinfo

 

 

Adding two doubles returns a double, adding a double and a bool returns a bool, and adding two bools returns an int.

I use in my examples only arithmetic types. If you want to apply my examples to user-defined that support arithmetic operations, you must extend my solutions.

Now, my journey starts with C++98.

C++98

Honestly, C++98 provides no general solution for returning the right type. Essentially, you must implement the type-deduction rules using a technique called traits also know as template traits. A traits class provides useful information about template parameters and can be used instead of the template parameters.

The following class ResultType provides a type-to-type mapping using full template specialization.

 

// traits.cpp

#include <iostream>
#include <typeinfo>

template <typename T, typename T2> // primary template (1)
struct ReturnType;       

template <>  // full specialization for double, double
struct ReturnType <double, double> {
    typedef double Type;
};

template <> //  full specialization for double, bool
struct ReturnType <double, bool> {
    typedef double Type;         // (2)
};

template <> // full specialization for bool, double
struct ReturnType <bool, double> {
    typedef double Type;
};

template <> // full specialization for bool, bool
struct ReturnType <bool, bool> {
    typedef int Type;
};

template <typename T, typename T2>  
typename ReturnType<T, T2>::Type sum(T t, T2 t2) {   // (3)
    return t + t2;
}

int main() {

    std::cout << '\n';

    std::cout << "typeid(sum(5.5, 5.5)).name(): " << typeid(sum(5.5, 5.5)).name() << '\n';
    std::cout << "typeid(sum(5.5, true)).name(): " << typeid(sum(5.5, true)).name() << '\n';
    std::cout << "typeid(sum(true, 5.5)).name(): " << typeid(sum(true, 5.5)).name() << '\n';
    std::cout << "typeid(sum(true, false)).name(): " << typeid(sum(true, false)).name() << '\n';

    std::cout << '\n';

}

 

Line (1) is the primary template or general template. The primary template has to be declared before the following full specializations. If the primary template is not needed, a declaration such as in line 1 is fine. The following lines provide the full specializations for <double, double> , for <double, bool>, for <bool, double>, and for <bool, bool>. You can read more details about template specialization in my previous posts:

The critical observation in the various full specializations of ReturnType is that they all have an alias Type such as typedef double Type (line 2). This alias is the return type of the function template sum (line 3): typename ReturnType<T, T2>::type.

The traits work as expected.

traits

 

You may be wondering why I used typename in the return type expression of the function template sum. At least one reader of my previous post about Dependent Names asked me when to apply typename or .template to templates. The short answer is that the compiler can not decide if the expression ReturnType<T, T2>::Type is a type (such as in this case), a non-type, or a template. Using typename before ReturnType<T, T2>::Type gives the compiler the crucial hint. You can read the long answer in my previous post Dependent Names.

Missing Overload

Originally, I wanted to continue my post and write about C++11 but I assume you have an additional question: What happens when I invoke the function template sum with arguments for which not partial template specialization is defined? Let me try it out with sum(5.5f, 5).

 

// traitsError.cpp

#include <iostream>
#include <typeinfo>

template <typename T, typename T2> // primary template
struct ReturnType;       

template <>  // full specialization for double, double
struct ReturnType <double, double> {
    typedef double Type;
};

template <> //  full specialization for double, bool
struct ReturnType <double, bool> {
    typedef double Type;
};

template <> // full specialization for bool, double
struct ReturnType <bool, double> {
    typedef double Type;
};

template <> // full specialization for bool, bool
struct ReturnType <bool, bool> {
    typedef int Type;
};

template <typename T, typename T2>  
typename ReturnType<T, T2>::Type sum(T t, T2 t2) {
    return t + t2;
}

int main() {

    std::cout << '\n';

    std::cout << "typeid(sum(5.5f, 5.5)).name(): " << typeid(sum(5.5f, 5.5)).name() << '\n';

    std::cout << '\n';

}

 

Many C++ programmers expect that the float value 5.5f is converted to an double and the full specialization for <double, double> is used. 

 traitsError

 

NO! The types must match exactly. The MSVC compiler gives an exact error message. There is no overload sum for T = float and T2 = double available. The primary template is not defined and can, therefore, not be instantiated.

Types do not convert, only expressions such as values can be converted: double res  = 5.5f + 5.5;

Default Return Type

When you make out of the declaration of the primary template a definition, the primary template becomes the default case. Consequently, the following implementation of ReturnType uses long double as the default return type.

// traitsDefault.cpp

#include <iostream>
#include <typeinfo>

template <typename T, typename T2> // primary template
struct ReturnType {
    typedef long double Type;     
};

template <>  // full specialization for double, double
struct ReturnType <double, double> {
    typedef double Type;
};

template <> //  full specialization for double, bool
struct ReturnType <double, bool> {
    typedef double Type;
};

template <> // full specialization for bool, double
struct ReturnType <bool, double> {
    typedef double Type;
};

template <> // full specialization for bool, bool
struct ReturnType <bool, bool> {
    typedef int Type;
};

template <typename T, typename T2>  
typename ReturnType<T, T2>::Type sum(T t, T2 t2) {
    return t + t2;
}

int main() {

    std::cout << '\n';

    std::cout << "typeid(sum(5.5, 5.5)).name(): " << typeid(sum(5.5, 5.5)).name() << '\n';
    std::cout << "typeid(sum(5.5, true)).name(): " << typeid(sum(5.5, true)).name() << '\n';
    std::cout << "typeid(sum(true, 5.5)).name(): " << typeid(sum(true, 5.5)).name() << '\n';
    std::cout << "typeid(sum(true, false)).name(): " << typeid(sum(true, false)).name() << '\n';
    std::cout << "typeid(sum(5.5f, 5.5)).name(): " << typeid(sum(5.5f, 5.5)).name() << '\n';

    std::cout << '\n';

}

 

The invocation of sum(5.5f, 5.f) causes the instantiation of the primary template.

 

 traitsDefault

 

 

What's next?

In C++11, there are various ways to automatically deduce the return type. C++14 adds syntactic sugar to these techniques, and C++20 enables it to write it very explicitly. Read more about the improvements in my next post.

 

Thanks a lot to my Patreon Supporters: Matt Braun, Roman Postanciuc, Tobias Zindl, Marko, G Prvulovic, Reinhold Dröge, Abernitzke, Frank Grimm, Sakib, Broeserl, António Pina, Sergey Agafyin, Андрей Бурмистров, Jake, GS, Lawton Shoemake, Animus24, Jozo Leko, John Breland, espkk, Louis St-Amour, Venkat Nandam, Jose Francisco, Douglas Tinkham, Kuchlong Kuchlong, Robert Blanch, Truels Wissneth, Kris Kafka, Mario Luoni, Neil Wang, Friedrich Huber, lennonli, Pramod Tikare Muralidhara, Peter Ware, Tobi Heideman, Daniel Hufschläger, Red Trip, Alexander Schwarz, Alessandro Pezzato, Evangelos Denaxas, Bob Perry, Satish Vangipuram, Andi Ireland, Richard Ohnemus, Michael Dunsky, Dimitrov Tsvetomir, Leo Goodstadt, Eduardo Velasquez, John Wiederhirn, Yacob Cohen-Arazi, Florian Tischler, Robin Furness, Michael Young, and Holger Detering.

 

Thanks in particular to Jon Hess, Lakshman, Christian Wittenhorst, Sherhy Pyton, Dendi Suhubdy, Sudhakar Belagurusamy, Richard Sargeant, and Rusty Fleming.

 

 

My special thanks to Embarcadero CBUIDER STUDIO FINAL ICONS 1024 Small

 

My special thanks to PVS-Studio PVC Logo

 

Seminars

I'm happy to give online seminars or face-to-face seminars worldwide. Please call me if you have any questions.

Bookable (Online)

German

Standard Seminars (English/German)

Here is a compilation of my standard seminars. These seminars are only meant to give you a first orientation.

New

Contact Me

Modernes C++,

RainerGrimmSmall

 
 

 

 

My Newest E-Books

Course: Modern C++ Concurrency in Practice

Course: C++ Standard Library including C++14 & C++17

Course: Embedded Programming with Modern C++

Course: Generic Programming (Templates)

Course: C++ Fundamentals for Professionals

Interactive Course: The All-in-One Guide to C++20

Subscribe to the newsletter (+ pdf bundle)

Blog archive

Source Code

Visitors

Today 711

Yesterday 8760

Week 9471

Month 205628

All 7691020

Currently are 163 guests and no members online

Kubik-Rubik Joomla! Extensions

Latest comments