3

I have a template class

template <typename T>
class foo;

There are 2 valid values for T, corresponding to:

using fooT1 = class foo<T1>;
using fooT2 = class foo<T2>;

I wanted to write code which looks like:

const auto* fooPtr = useFooT1 ? getFooT1Ptr() : getFooT2Ptr();

because the code using fooPtr in this function doesn't depend on whether fooPtr is of type fooT1 or fooT2

However, I get the following compiler error:

error: conditional expression between distinct pointer types ...

I understand that as per the C++ standard, there should be a common type that both can be casted to, so this approach may not work.

What's a good way to achieve this functionality without replicating a lot of code?

vigs1990
  • 1,639
  • 4
  • 17
  • 19
  • 1
    Split that code using `fooPtr` into a separate function template that's templatized on the pointer's type? – T.C. Apr 12 '15 at 09:38
  • @T.C. Yup that was one option on my mind. Was wondering if there are any other alternatives that I've missed. – vigs1990 Apr 12 '15 at 09:41
  • 1
    @vigs1990 I have such thing as a reminder on my blog: http://dev-jungle.blogspot.de/2014/02/conditional-type-selection-using-c.html – πάντα ῥεῖ Apr 12 '15 at 09:43

3 Answers3

2

Indeed, C++ is statically typed, so the type of a variable can't depend on a runtime condition.

Instead, put the generic code into a template:

template <typename T> doStuff(foo<T> * f) {
    // stuff that works with any `foo` type
}

and call a different specialisation depending on the run-time variable

if (useFooT1) {
    doStuff(getFooT1Ptr());
} else {
    doStuff(getFooT2Ptr());
}
Mike Seymour
  • 249,747
  • 28
  • 448
  • 644
2

If the boolean variable useFooT1 is known at compile time, you could implement a compile-time switch, like

FooT1 const* getFooPtr(std::true_type /* useFooT1 */) { 
    return getFooT1Ptr(); 
}
FooT2 const* getFooPtr(std::false_type /* !useFooT1 */) { 
    return getFooT2Ptr(); 
}
/* ... */
auto const* fooPtr = getFooPtr(std::integral_constant<bool, useFooT1>());

This can also be generalized for more than two types, and the switch could depend on type T1/T2 directly.

See also: Is it possible to use tag dispatching to determine return type and Switch passed type from template and many others.

Community
  • 1
  • 1
spraetor
  • 441
  • 4
  • 13
0

Below is an example (actually 2 different methods: V1/V2) how you can use one function that can internally use different types of std:: containers. Note that the type of container used can actually alter the outcome of the function (because of internal ordering of the container), which is what I show here. The function returnMaxPointV1/2 takes a bool argument and internally has partly generic code and partly specialized code. This is the opposite approach as using a template, where the function caller must be specialized (! at compile time !), but the function body is generic. Here the caller is generic (! at runtime !), but the body is specialized.

#include <vector>
#include <set>
#include <iostream>
#include <thread>         // std::this_thread::sleep_for
#include <chrono>         // std::chrono::seconds

struct Point2D {
    int x1;
    int x2;

    bool operator < (const Point2D& rhs) const {
        return x1-10*x2 < rhs.x1-10*rhs.x2;
    }
};


Point2D points[4] = { { 10,2 },{ 2,2 },{ 0,1 },{ 10,4 } };


namespace returnMaxInlines
{
    inline  Point2D FindMax(Point2D a, Point2D b) {
        return (a.x1 > b.x1) ? a : b;
    }
}

/*
The function returnMaxPoint fills a container with points that satisfy the condition x2 >= 2.
You can select what kind of containter to use  by using the bool containterTypeSet
the function then finds the point with maximum x1 in the container, but because
there are some points with the same x1 it will return the first one encountered,
which may be a different one depending on the container type,
because the points may be differently ordered in the container.
*/
Point2D returnMaxPointV1(bool containterTypeSet)
{
    using namespace returnMaxInlines;       // the inline function is only accessible locally in this function (because of the namespace)

    Point2D     maxPoint = { 0,0 };
    if (containterTypeSet == true) {
        std::set<Point2D>   container;
        for (int cnt = 0; cnt < 4; ++cnt) {
            if (points[cnt].x2 >= 2) {
                container.insert(points[cnt]);
            }
        }
        for (auto it = container.begin(); it != container.end(); ++it) {
            maxPoint = FindMax(maxPoint, *it);      // this part of the code is generic for both cases of bool containterTypeSet, it is an inline function
        }
    }
    else {
        std::vector<Point2D>    container;
        for (int cnt = 0; cnt < 4; ++cnt) {
            if (points[cnt].x2 >= 2) {
                container.push_back(points[cnt]);
            }
        }
        for (auto it = container.begin(); it != container.end(); ++it) {
            maxPoint = FindMax(maxPoint, *it);      // this part of the code is generic for both cases of bool containterTypeSet, it is an inline function
        }
    }
    return maxPoint;
}

/* Alternative implementation
    No inline function is needed, but you need to have all variables for both options declared in scope
    and you need to add many if-statements, thus branching (but maybe the compiler can optimize this out (which will yield some kind of version V1)
*/

Point2D returnMaxPointV2(bool containterTypeSet)
{
    Point2D                 maxPoint = { 0,0 };
    std::set<Point2D>       containerSet;
    std::vector<Point2D>    containerVector;

    // specialized code
    if (containterTypeSet) {
        for (int cnt = 0; cnt < 4; ++cnt) {
            if (points[cnt].x2 >= 2) {
                containerSet.insert(points[cnt]);
            }
        }
    }
    else{
        for (int cnt = 0; cnt < 4; ++cnt) {
            if (points[cnt].x2 >= 2) {
                containerVector.push_back(points[cnt]);
            }
        }
    }
    std::set<Point2D>::iterator         SetIterator;
    std::vector<Point2D>::iterator      VectorIterator;

    const Point2D   *ScopePoint;
    bool            foolCompiler;       // the compiler was tripping if both types of the tenary operator (?:) were not of the same type, so I made the statement such that the statement was of type bool.

    for(foolCompiler = containterTypeSet? ((SetIterator = containerSet.begin()) == SetIterator): ((VectorIterator = containerVector.begin()) == VectorIterator);
        containterTypeSet ? SetIterator != containerSet.end() : VectorIterator != containerVector.end();
        foolCompiler = containterTypeSet ? ((SetIterator++) == SetIterator) : ((VectorIterator++) == VectorIterator)
        ){
        ScopePoint = containterTypeSet ? &(*SetIterator) : &(*VectorIterator);
        // generic code 
        maxPoint = (maxPoint.x1 > ScopePoint->x1) ? maxPoint : *ScopePoint;
    }

    return maxPoint;
}

int main()
{
    Point2D result;

    result = returnMaxPointV1(true);
    std::cout << "result1: (" << result.x1 << "," << result.x2 << ")" << std::endl;
    result = returnMaxPointV1(false);
    std::cout << "result2: (" << result.x1 << "," << result.x2 << ")" << std::endl;

    result = returnMaxPointV2(true);
    std::cout << "result3: (" << result.x1 << "," << result.x2 << ")" << std::endl;
    result = returnMaxPointV2(false);
    std::cout << "result4: (" << result.x1 << "," << result.x2 << ")" << std::endl;

    std::this_thread::sleep_for(std::chrono::seconds(10));
}

The output of the program is:

result1: (10,2)
result2: (10,4)
result3: (10,2)
result4: (10,4)
isgoed
  • 724
  • 6
  • 12