I'm trying to bind some ta-lib functions and then callback.
Here is the simplified sample code:
#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) {});
}
How can I make them unambiguous, that is, something like preventing std::function<int(int)>
/int (*)(int)
from being converted to std::function<int(double)>
?
I tried to prefix explicit
keyword to the constructors, still it doesn't prevent ambiguousness.
Letting the appropriate std::function
objects be constructed implicitly obviously fails – alternatives (explicitly construct std::function
objects or factory functions) are a bit wordy at the user side (i.e. when constructing the X
objects). If you prefer offering more comfort there you need to invest more effort at the implementation; one possible approach there is making the constructor a template accepting arbitrary types and apply static type checks to determine if the right parameters have been passed to. This might look as follows. At first we need some further traits to be able to determine which parameter the functions passed to actually accept:
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;
Now we can use these in a template constructor:
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;
}
}
}
The code will fail if non-matching functions are passed (either with differing ParamType
or not even matching the signatures of the std::function
types in question, otherwise elegantly create the objects intended via ordinary constructor calls, see demonstration on godbolt.