在 C++ 中复制 Haskell 的 Return 类型重载(通过类型类)

Replicate Haskell's Return Type Overloading (via Typeclasses) in C++

在 Haskell 中,类型类允许您优雅地重载基于 return 类型的函数。对于参数和 return 类型都重载的情况,使用模板(示例 A)在 C++ 中复制它是微不足道的:

template <typename In, typename Out> Out f(In value);

template <typename T> int f<T, int>(T value) {
   ...
}

对应于Haskell的:

class F a b where
    f :: a -> b

您甚至可以在大多数函数上仅重载 return 类型(示例 B):

template <typename Out> Out f(SomeClass const &value);

template <> inline int f(SomeClass const &value) {
    return value.asInt();
}

template <> inline float f(SomClass const &value) {
    return value.asFloat();
}

对应于:

class F a where
    f :: SomeData -> a

但我希望能够做的是修改最后一个示例以重载高阶类型,即 C++ 中的模板化结构。也就是说,我希望能够编写类似于以下 Haskell:

的专业化
data Foo a = Foo a

instance F (Foo a) where
    f someData = Foo $ ...

如何编写具有此功能的模板(甚至可能)?


作为参考,我打算用它来为 Lua/C++ 桥编写模板函数。这个想法是通过重载函数 interpretValue 在 Lua 和 C++ 函数之间架起桥梁,自动从 Lua 堆栈压入或转换。对于具有直接内置 Lua 表示的简单类型,使用示例 B 等代码就足够简单了。

对于更复杂的类型,我还写了一个 template <typename T> struct Data 来处理对象的内存管理(桥接 Lua 的 GC 和 C++ 端引用计数),我希望能够重载 interpretValue 以便它可以自动将用户数据指针包装到 Data<T> 中。我尝试使用以下内容,但 clang 给出了 "function call is ambiguous" 错误:

template <typename U> inline U &interpretValue(lua_State *state, int index) {
    return Data<U>::storedValueFromLuaStack(state, index);
}

template <typename U> inline Data<U> interpretValue(lua_State *state, int index) {
    return Data<U>::fromLuaStack(state, index);
}

谢谢!

嗯,你可以写一个函数:

template <class U>
interpretValueReturnType<U> interpretValue(lua_State *state, int index)
{
    return interpretValueReturnType<U>(state, index);
}

然后,您需要使用转换运算符编写此 return 类型,因此,您会得到想要的结果:

template <class U>
class interpretValueReturnType
{
public:
    interpretValueReturnType(lua_State *state, int index) : state(state), index(index) {}

    operator U& () &&
    {
        return Data<U>::storedValueFromLuaStack(state, index);
    }

    operator Data<U> () &&
    {
        return Data<U>::fromLuaStack(state, index);
    }
private:
    lua_State *state;
    int index;
};

参见ideone

int main() {
    lua_State *state;
    int& a = interpretValue<int>(state, 1);
    Data<int> b = interpretValue<int>(state, 1);
}

operator 声明末尾的这个有趣的 && 是为了让存储这个函数的结果有点困难并在以后使用它 - 就像这里:

    auto c = interpretValue<float>(state, 1);
    float& d = c; // not compile

需要使用 std::move 因为 && 意味着函数只能用于右值引用:

    auto c = interpretValue<float>(state, 1);
    float& d = std::move(c);