提问者:小点点

如何防止具有不同参数类型的' std::function '之间的隐式转换?


我正在尝试绑定一些ta-lib函数,然后进行回调。

下面是简化的示例代码:

#include <functional>
#include <type_traits>
#include <cstdint>

struct DataChunk {
    // ...
};
typedef uint64_t idx_t;

template <typename LookbackArgType> // int, double
struct talib_traits {
    using talib_lookback_t = std::function<int(LookbackArgType)>;
    using talib_function_t = std::function<void(DataChunk &, void *, idx_t, idx_t, LookbackArgType)>;
};

template <>
struct talib_traits<void> {
    using talib_lookback_t = std::function<int()>;
    using talib_function_t = std::function<void(DataChunk &, void *, idx_t, idx_t)>;
};

struct X {
    talib_traits<int>::talib_lookback_t talib_lookback_int = nullptr;
    talib_traits<int>::talib_function_t talib_function_int = nullptr;

    talib_traits<double>::talib_lookback_t talib_lookback_double = nullptr;
    talib_traits<double>::talib_function_t talib_function_double = nullptr;

    talib_traits<void>::talib_lookback_t talib_lookback_void = nullptr;
    talib_traits<void>::talib_function_t talib_function_void = nullptr;

    explicit X(talib_traits<int>::talib_lookback_t talib_lookback, talib_traits<int>::talib_function_t talib_function)
        : talib_lookback_int(talib_lookback), talib_function_int(talib_function) {
    }
    explicit X(talib_traits<double>::talib_lookback_t talib_lookback,
               talib_traits<double>::talib_function_t talib_function)
        : talib_lookback_double(talib_lookback), talib_function_double(talib_function) {
    }
    explicit X(talib_traits<void>::talib_lookback_t talib_lookback, talib_traits<void>::talib_function_t talib_function)
        : talib_lookback_void(talib_lookback), talib_function_void(talib_function) {
    }
};

int main() {
    constexpr bool lookback_is_same =
        std::is_same<talib_traits<int>::talib_lookback_t, talib_traits<double>::talib_lookback_t>::value;
    constexpr bool function_is_same =
        std::is_same<talib_traits<int>::talib_function_t, talib_traits<double>::talib_function_t>::value;
    static_assert(!lookback_is_same && !function_is_same);

    X x([](void) { return 0; }, [](DataChunk &, void *, idx_t, idx_t) {}); // okay

    // ambiguous: more than one instance of constructor "X::X" matches the argument list, int or double?
    X y([](int) { return 0; }, [](DataChunk &, void *, idx_t, idx_t, int) {});
}

我怎样才能使它们明确,即防止 std::function

我尝试在构造函数前面加explicit关键字,但这并不能防止歧义。


共3个答案

匿名用户

我认为你不能阻止一个< code>std::函数

可以通过在调用站点将lambda转换为std::函数来避免这种情况。在这种情况下,您不需要为std::function指定类型参数:

X y(std::function([](int) { return 0; }),
    [](DataChunk &, void *, idx_t, idx_t, int) {});

现在,使用std::函数的构造函数

人们希望在将lambda转换为std::f时需要额外的返回类型转换

或者,如果将构造函数转换为模板化工厂函数,则可以显式指定类型(godbolt):

struct X {
    ...

    template <typename T>
    static X create(typename talib_traits<T>::talib_lookback_t talib_lookback,
             typename talib_traits<T>::talib_function_t talib_function) {
        X out;
        if constexpr(std::is_same_v<T, int>) {
            out.talib_lookback_int = talib_lookback;
            out.talib_function_int = talib_function;
        } else if constexpr(std::is_same_v<T, double>) {
            out.talib_lookback_double = talib_lookback;
            out.talib_function_double = talib_function;
        } else {
            static_assert(std::is_same_v<T, void>);
            out.talib_lookback_void = talib_lookback;
            out.talib_function_void = talib_function;
        }
        return out;
    }
    
private:
    X(){};
};

int main() {
    X x = X::create<void>([](void) { return 0; }, [](DataChunk &, void *, idx_t, idx_t) {}); // okay
    X y = X::create<int>([](int){ return 0; }, [](DataChunk &, void *, idx_t, idx_t, int) {});
    X z = X::create<double>([](double){ return 0; }, [](DataChunk &, void *, idx_t, idx_t, double) {});
}

匿名用户

让适当的std::function对象被隐式构造显然是失败的——替代方案(显式构造std::function对象或工厂函数)在用户端有点冗长(即在构造X对象时)。如果你更喜欢在那里提供更多的舒适性,你需要在实现上投入更多的精力;一种可能的方法是使构造函数成为接受任意类型的模板,并应用静态类型检查来确定是否传递了正确的参数。这可能看起来如下所示。首先,我们需要一些进一步的特征来确定传递的函数实际上接受哪个参数:

template <typename T>
struct FunctionTraits;
template <typename T>
struct FunctionTraits<std::function<int(T)>>
{
    using ParameterType = T;
};
template <typename T>
struct FunctionTraits<std::function<void(DataChunk &, void *, idx_t, idx_t, T)>>
{
    using ParameterType = T;
};
template <>
struct FunctionTraits<std::function<int()>>
{
    using ParameterType = void;
};
template <>
struct FunctionTraits<std::function<void(DataChunk &, void *, idx_t, idx_t)>>
{
    using ParameterType = void;
};
template <typename T>
using ParameterType
= typename FunctionTraits<decltype(std::function(std::declval<T>()))>::ParameterType;

现在我们可以在模板构造函数中使用这些:

struct X
{
   // ...

    template <typename Lookback, typename Function>
    X(Lookback l, Function f)
    {
        using PType = ParameterType<decltype(l)>;
        static_assert(std::is_same_v<PType, ParameterType<decltype(f)>>);
        if constexpr(std::is_same_v<PType, int>)
        {
            talib_lookback_int = l;
            talib_function_int = f;
        }
        else if constexpr(std::is_same_v<PType, double>)
        {
            talib_lookback_double = l;
            talib_function_double = f;
        }
        else if constexpr(std::is_same_v<PType, void>)
        {
            talib_lookback_void = l;
            talib_function_void = f;
        }
    }
}

如果传递了不匹配的函数(使用不同的ParamType或者甚至不匹配所讨论的std::function类型的签名),代码将失败,否则将通过普通构造函数调用优雅地创建预期的对象,请参阅Godbolt上的演示。

匿名用户

受Aconcagua巧妙回答的启发,您可以使用模板和< code>std::enable_if来禁用不必要的重载:

template<typename F, typename R, typename... Args>
constexpr bool is_functype =
    std::is_same_v<decltype(std::function(std::declval<F>())),
                   std::function<R(Args...)>>;

struct X {
    talib_traits<int>::talib_lookback_t talib_lookback_int = nullptr;
    talib_traits<int>::talib_function_t talib_function_int = nullptr;

    talib_traits<double>::talib_lookback_t talib_lookback_double = nullptr;
    talib_traits<double>::talib_function_t talib_function_double = nullptr;

    talib_traits<void>::talib_lookback_t talib_lookback_void = nullptr;
    talib_traits<void>::talib_function_t talib_function_void = nullptr;

    template<typename F, std::enable_if_t<is_functype<F, int, int>, int> = 0>
    X(F talib_lookback,
      talib_traits<int>::talib_function_t talib_function)
    : talib_lookback_int(talib_lookback),
      talib_function_int(talib_function)
    {}

    template<typename F, std::enable_if_t<is_functype<F, int, double>, int> = 0>
    X(F talib_lookback,
      talib_traits<double>::talib_function_t talib_function)
    : talib_lookback_double(talib_lookback),
      talib_function_double(talib_function)
    {}
};

戈德博尔特