0

I have a templated method of a Lua State class, which tests if the object at a given index is of a given type:

template<typename T> bool is(int idx) {
    return luaL_testudata(L, idx, std::remove_pointer<T>::type::name) != NULL;
};

and a few specializations:

template<> inline bool State::is<bool>(int i){ return lua_isboolean(this->L, i); }
template<> inline bool State::is<int>(int i){ return lua_isinteger(this->L, i); }
//etc...

Now I want to add a specialization for any class that has a static "lua_test" method:

template<> inline bool State::is<T>(int i){ return T::lua_test(this->L, i); }

But I don't know the correct syntax for this. The closest I've come is:

template <class T>
inline auto State::is(int idx) ->
decltype(std::declval<T>.lua_test(this, idx), true)
{
    return T::lua_test(this, idx);
}

This fails to compile, because apparently the types don't match:

templates.hpp:87:13: error: prototype for ‘decltype ((declval<T>.lua_test(((Lua::State*)this), idx), true)) Lua::State::is(int)’ does not match any in class ‘Lua::State’
 inline auto State::is(int idx) ->
             ^~~~~
In file included from [...],
                 from debug.cpp:1:
get.hpp:6:27: error: candidate is: template<class T> bool Lua::State::is(int)
 template<typename T> bool is(int idx) {

The idea is supposed to be that the decltype statement evaluates to decltype(true) (due to the comma operator), ie bool, but gcc doesn't seem convinced of this.

This seems like it should be pretty simple, but I've been getting nowhere trying to implement it.

Rena
  • 606
  • 7
  • 21
  • 2
    You have forgotten the function call on std::declval (it should be `decltype(std::declval().lua_test(this, idx), true)`), maybe this is the problem, but I'm not sure. You might have to also add the decltype in the class definition. – Corristo Sep 28 '16 at 05:41

2 Answers2

1

The class method is() will be called always with explicit template parameters, such as: is<some_type>(value). Hence in such case you may enclose it as a static function into a class for simplicity of template specialization.

template<typename T, typename = void>
struct X {   // default
  static bool is (int idx) { return ...; } 
};

template<>
struct X<int, void> {   // `int` specialization
  static bool is (int idx) { return lua_isinteger(...); } 
};

template<>
struct X<bool, void> {   // `bool` specialization
  bool is (int idx) { return lua_isboolean(...); } 
};

Specialization:

Now we have reserved typename = void for ease of SFINAE as following:

template<typename T> struct void_ { using type = void; };

template<typename T>  // specialization for those, who contain `lua_test`
struct X<T, typename void_<decltype(&T::lua_test)>::type> { 
  static bool is (int idx) { return T::lua_test(...); } 
};

Usage:

X<some_class>::is(0);
X<int>::is(1);
X<bool>::is(2);
X<some_class_with_lua_test_function>::is(3);

It compiles & works in my environment and hence, it should work for you too!


Edit: I see that you are using this inside this method. In such case your method may look like:

template<typename T, typename = void>
struct X {   // default
  static bool is (Lua* const this_, int idx) { return ...; } 
};
// ... other specializations with similar syntax
iammilind
  • 68,093
  • 33
  • 169
  • 336
1

You may simply repeat the return in the decltype:

struct State
{
    // ....

    template <class T>
    auto is(int idx) -> decltype(T::lua_test(this, idx))
    {
        return T::lua_test(this, idx);
    }
};

And for your case, you have to do some work to don't have ambiguous call between the two method.

struct low_priority {};
struct high_priority : low_priority {};

struct State
{
public:
    template<typename T>
    bool is(int idx) {
        return is_impl<T>(idx, high_priority{});
    }

private:
    template<typename T>
    bool is_impl(int idx, low_priority) {
        std::cout << "generic\n";
        return luaL_testudata(L, idx, std::remove_pointer<T>::type::name) != NULL;
    }

    template <class T>
    auto is_impl(int idx, high_priority) -> decltype(T::lua_test(this, idx))
    {
        std::cout << "LuaTest\n";
        return T::lua_test(this, idx);
    }

    LuaState L;
};

Demo

Jarod42
  • 203,559
  • 14
  • 181
  • 302
  • Will this even compile? This will directly collide with the original syntax `template bool is(int idx);` and will result in compilation error for multiple definition of same signature. The OP wants an `is()` method which should be specialized/overloaded for the classes which has `static lua_test()` inside them. – iammilind Sep 28 '16 at 08:42
  • @iammilind: It compiles, but indeed, it would create an ambiguous call. Demo added to show how to handle all cases. – Jarod42 Sep 28 '16 at 11:42
  • Nice. I was not aware of `decltype` based `template` specialization. It will be good if you add another example which invokes normal version. Does this compile with C++11 as per question? – iammilind Sep 28 '16 at 12:43
  • I think you want to replace `std::remove_point::type::name` with `std::remove_pointer_t{}`. Calling `s.is(42);` fails to compile otherwise. – AndyG Sep 28 '16 at 15:24
  • @AndyG: I kept the OP's implementation for the generic case (which indeed doesn't work for `char` but might work for custom type with a `static` `name`). – Jarod42 Sep 28 '16 at 17:08
  • Thanks, I think I understand how that works. Hopefully it won't affect runtime performance? – Rena Sep 28 '16 at 18:49
  • 1
    @Rena: All the dispatching can easily be optimized by compiler and so have no extra cost. – Jarod42 Sep 28 '16 at 19:37