Tomasu
Tomasu

Reputation: 181

function specialization based on if template parameter is shared_ptr

I'm trying to detect if a type is a shared_ptr<T> and if it is, dispatch to a specific function template or override.

Here's a simplified version of what I'm actually attempting:

#include <type_traits>
#include <memory>
#include <cstdio>

template <class T> struct is_shared_ptr : std::false_type {};
template <class T> struct is_shared_ptr<std::shared_ptr<T> > : std::true_type {};

class Foo { };
typedef std::shared_ptr<Foo> SharedFoo;

template<class T> void getValue();

template<class T, typename std::enable_if<is_shared_ptr<T>::value>::type = 0>
void getValue()
{
    printf("shared!\n");
}

template<class T, typename std::enable_if<!is_shared_ptr<T>::value>::type = 0>
void getValue()
{
    printf("not shared!\n");
}

int main(int, char **)
{
    getValue<SharedFoo>();
    getValue<Foo>();
    return 0;
}

It compiles just fine, but it seems the actual functions were never actually generated because the code doesn't link with the following errors:

/tmp/ccjAKSBE.o: In function `main':
shared_test.cpp:(.text+0x10): undefined reference to `void getValue<std::shared_ptr<Foo>>()'
shared_test.cpp:(.text+0x15): undefined reference to `void getValue<Foo>()'
collect2: error: ld returned 1 exit status

I would think that those would be covered by the two function templates. But they aren't.

Given that, it seems I am seriously misunderstanding something.

So maybe it would help if I explain what I'm /trying/ to do rather than what I'm actually doing.

I have some "magic" code using a bunch of new (to me) C++11 features to bind C++ code to lua (can be seen here: https://github.com/Tomasu/LuaGlue). someone has recently asked for support for binding to classes wrapped in shared_ptr's. which is not something that works at the moment, because it binds at compile time using templates and tuple unwrapping to generate code to call functions on either the C++ or lua side. In the "magic" unwrapping code, I have a bunch of overridden and "specialized" functions that handle various variable types. Some for basic types, one for static objects, and another for pointer to objects. A shared_ptr can't be handled in the same way as either a static or pointer object, so I need to add some extra handling just for them.

For example:

template<typename T>
T getValue(LuaGlue &, lua_State *, unsigned int);

template<>
int getValue<int>(LuaGlue &, lua_State *state, unsigned int idx)
{
    return luaL_checkint(state, idx);
}

template<class T>
T getValue(LuaGlue &g, lua_State *state, unsigned int idx)
{
    return getValue_<T>(g, state, idx, std::is_pointer<T>());
}

That's the actual code (notice the hairy template/override via function argument :-x).

I had thought it'd be as simple as adding another addValue function, along the lines of the code in my earlier example, via enable_if.

Upvotes: 5

Views: 5466

Answers (3)

Cassio Neri
Cassio Neri

Reputation: 20523

Here is another solution which is basically a fix for the OP rather than alternatives (using a class and its constructor as in zennehoy's post or tag dispatch as in Casey's).

// Do not provide this declaration:
// template<class T> void getValue();

template<class T>
typename std::enable_if<is_shared_ptr<T>::value>::type
getValue()
{
    printf("shared!\n");
}

template<class T>
typename std::enable_if<!is_shared_ptr<T>::value>::type
getValue()
{
    printf("not shared!\n");
}

I must say that, as Casey has put it, I also "prefer the simplicity of tag dispatch". Hence, I would go for his solution.

I also found zennehoy's idea interesting but, unfortunately, it doesn't allow your function to return something. (I believe a workaround is possible but it adds complexity.) With the solution here you just need to provide the return type as the 2nd parameter of std::enable_if.

Upvotes: 0

Casey
Casey

Reputation: 42554

Your program declares template<class T> void getValue() but doesn't define it. template <typename, typename> void getValue() is a different function. The compiler is picking template<class T> void getValue() as a better match with your invocations, which of course fails at link time as there is no definition of that function.

I prefer the simplicity of tag dispatch when it's applicable (live at Coliru):

template<class T>
void getValue(std::true_type)
{
    printf("shared!\n");
}

template<class T>
void getValue(std::false_type)
{
    printf("not shared!\n");
}

template<class T>
void getValue() {
    return getValue<T>(is_shared_ptr<T>{});
}

Upvotes: 2

zennehoy
zennehoy

Reputation: 6856

Any reason not to simply use partial specialization?

#include <type_traits>
#include <memory>
#include <cstdio>

class Foo { };
typedef std::shared_ptr<Foo> SharedFoo;

template <class T>
struct getValue {
    getValue() {
        printf("not shared!\n");
    }
};

template <class T>
struct getValue<std::shared_ptr<T> > {
    getValue() {
        printf("shared!\n");
    }
};

int main(int, char **)
{
    getValue<SharedFoo>();
    getValue<Foo>();
    return 0;
}

Upvotes: 7

Related Questions