Replies: 6 comments 2 replies
-
A better implementation with very short error message still carryign informations Tag Invoke pour EVE#include <utility>
#include <type_traits>
#include <raberu.hpp>
#include <eve/concept/value.hpp>
//-------------------------------------------------------------------------------------------------
// Straight Outta The N Paper
//-------------------------------------------------------------------------------------------------
namespace eve
{
namespace func_ns
{
struct dispatcher
{
template<typename Tag, typename... Args>
requires requires (Tag&& tag, Args&&... args) { tag_dispatch(EVE_FWD(tag), EVE_FWD(args)...); }
EVE_FORCEINLINE
constexpr auto operator()(Tag&& tag, Args&&... args) const
noexcept(noexcept(tag_dispatch(EVE_FWD(tag), EVE_FWD(args)...)))
-> decltype(tag_dispatch(EVE_FWD(tag), EVE_FWD(args)...))
{
return tag_dispatch(EVE_FWD(tag), EVE_FWD(args)...);
}
};
}
inline namespace callable_ns { inline constexpr func_ns::dispatcher tag_dispatch = {}; }
template<typename Tag, typename... Args>
concept tag_invocable = requires(Tag&& tag, Args&&... args)
{
eve::tag_dispatch(EVE_FWD(tag), EVE_FWD(args)...);
};
template<typename Tag, typename... Args>
using tag_dispatch_result = std::invoke_result<decltype(eve::tag_dispatch), Tag, Args...>;
template<typename Tag, typename... Args>
using tag_dispatch_result_t = std::invoke_result_t<decltype(eve::tag_dispatch), Tag, Args...>;
template<auto& Tag> using tag = std::decay_t<decltype(Tag)>;
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
// Local arch detection/emulation
//-------------------------------------------------------------------------------------------------
namespace eve::arch
{
inline constexpr struct cpu_ {} cpu{};
inline constexpr struct asimd_ : cpu_ {} asimd{};
inline constexpr struct sse2_ : cpu_ {} sse2{};
inline constexpr struct sse4_ : sse2_ {} sse4{};
};
#include <iostream>
//-------------------------------------------------------------------------------------------------
// Tools for overload
//-------------------------------------------------------------------------------------------------
namespace eve
{
// Delay overload resolution in functions
namespace detail { inline constexpr struct delay_t {} delay {}; }
// Retrieve the current architecture (no macro)
inline constexpr auto current_arch = []()
{
if constexpr(spy::simd_instruction_set == spy::sse41_ ) return arch::sse4;
else if constexpr(spy::simd_instruction_set == spy::asimd_ ) return arch::asimd;
else return arch::sse2;
}();
using curren_arch_t = std::remove_cvref_t<decltype(current_arch)>;
namespace tags
{
template<typename Tag,typename... T> struct unsupported_call {};
}
//-----------------------------------------------------------------------------------------------
// Base class for masking support (no more macro)
//-----------------------------------------------------------------------------------------------
template<typename Tag> struct support_options
{
template<typename Settings> struct custom_fn
{
template<typename Options>
EVE_FORCEINLINE auto operator[](Options&& o) const
{
auto new_opts = rbr::merge(rbr::settings{o}, opts);
return custom_fn<decltype(new_opts)>{new_opts};
}
EVE_FORCEINLINE auto operator[](bool m) const
{
using namespace rbr::literals;
return (*this)["mask"_kw = m];
}
template<typename... Args>
EVE_FORCEINLINE auto operator()(Args const&... x) const
-> eve::tag_dispatch_result_t<Tag, curren_arch_t, Settings const&, Args const&...>
{
return tag_dispatch(Tag{}, current_arch, opts, x...);
}
tags::unsupported_call<Tag> operator()(...) const = delete;
Settings opts;
};
template<rbr::concepts::option Options>
EVE_FORCEINLINE auto operator[](Options&& o) const
{
auto n = rbr::settings{o};
return custom_fn<decltype(n)>{n};
}
template<rbr::concepts::settings Settings>
EVE_FORCEINLINE auto operator[](Settings const& s) const
{
return custom_fn<Settings>{s};
}
EVE_FORCEINLINE auto operator[](bool m) const
{
using namespace rbr::literals;
return (*this)["mask"_kw = m];
}
};
//-----------------------------------------------------------------------------------------------
// Type markings & concepts for function familly
//-----------------------------------------------------------------------------------------------
struct elementwise { using elementwise_tag = void; using callable_tag = void; };
struct reduction { using reduction_tag = void; using callable_tag = void; };
template<typename T>
concept callable = requires(T) { typename T::callable_tag_type; };
template<typename T>
concept elementwise_callable = callable<T> && requires(T) { typename T::elementwise_tag; };
template<typename T>
concept reduction_callable = callable<T> && requires(T) { typename T::reduction_tag; };
//-------------------------------------------------------------------------------------------------
// Complete the CPO interface by:
// -> tie-ing it to a function_ to limit rewriting effort
// -> limit compile-time with CPO solving tag_dispatch over too many overloads
// -> provide the stream insertion operator
//-------------------------------------------------------------------------------------------------
#define EVE_MAKE_CALLABLE_OBJECT(TYPE,NAME) \
static auto deferred_call(auto arch, auto&&... args) noexcept \
-> decltype(NAME ## _(detail::delay, arch, EVE_FWD(args)...)) \
{ \
return NAME ## _(detail::delay, arch, EVE_FWD(args)...); \
} \
template<typename Stream> \
requires requires(Stream& os) { os << #NAME; } \
friend Stream& operator<<(Stream& os, TYPE const& tag) { return os << #NAME; } \
using callable_tag_type = TYPE \
/**/
// Callable object lives in tag so that the tag namesapce can be used to park overloads
namespace tags
{
struct compute_fn : support_options<compute_fn>
, elementwise
{
// That's the only macro left
EVE_MAKE_CALLABLE_OBJECT(compute_fn, compute);
// Local poison pill - Stop right there: no lengthy error
template<typename... T>
unsupported_call<compute_fn, T...> operator()(T const&... x) const noexcept
requires(!requires{ tag_dispatch(*this, current_arch, x...); })
= delete;
template<typename... T>
auto operator()(T const&... x) const noexcept
-> decltype(tag_dispatch(*this, current_arch, x...))
{
// We add the arch to the call
return tag_dispatch(*this, current_arch, x...);
}
};
}
// The object lives in eve (no macro)
inline constexpr tags::compute_fn compute = {};
}
// Let's pretend we have a type on which we want to overload some functions
namespace eve
{
template<eve::scalar_value T>
struct fake_wide
{
public:
friend std::ostream& operator<<(std::ostream& os, fake_wide const& v)
{
return os << "fw{" << v.value_ << "}";
}
T value_;
// tag_dispatch can be private we don't care
private:
// Let's make the auto -> decltype mandatory for cleaner error reporting
template<callable Tag>
friend auto tag_dispatch(Tag , auto arch, const fake_wide& x) noexcept
-> decltype(Tag::deferred_call(arch, x))
{
return Tag::deferred_call(arch, x);
}
template<rbr::concepts::settings S, callable Tag>
friend auto tag_dispatch(Tag , auto arch, S const& opts, const fake_wide& x) noexcept
-> decltype(Tag::deferred_call(arch, opts, x))
{
return Tag::deferred_call(arch, opts, x);
}
template<rbr::concepts::settings S, callable Tag>
friend auto tag_dispatch(Tag , auto arch, S const& opts, bool b, const fake_wide& x) noexcept
-> decltype(Tag::deferred_call(arch, opts, b, x))
{
return Tag::deferred_call(arch, opts, b, x);
}
};
namespace detail
{
using namespace rbr::literals;
// This is the equivalent of our current overload system
template<eve::scalar_value T> auto compute_(detail::delay_t, arch::cpu_,const fake_wide<T>& x)
{
std::cout << __LINE__ << " : compute(fake_wide)\n";
return fake_wide<T>{x.value_*x.value_};
}
// This is the equivalent of our current overload system
template<rbr::concepts::settings S, eve::scalar_value T>
auto compute_(detail::delay_t, arch::cpu_, S const& opts, const fake_wide<T>& x)
{
std::cout << __LINE__ << " : any arch::compute(options, fake_wide)\n";
if constexpr(S::contains("mask"_kw))
{
auto fn = compute[rbr::drop("mask"_kw,opts)];
return fn(opts["mask"_kw],x);
}
else if constexpr(S::contains("pedantic"_fl))
{
std::cout << __LINE__ << " : pedantic behavior\n";
return fake_wide<T>{-7*x.value_};
}
else
{
std::cout << __LINE__ << " : not supported options\n";
return fake_wide<T>{1./x.value_};
}
}
// This is the equivalent of our current overload system
template<rbr::concepts::settings S, eve::scalar_value T>
auto compute_(detail::delay_t, arch::cpu_, S const&, bool b, const fake_wide<T>& x)
{
std::cout << __LINE__ << " : any arch::compute_(mask, fake_wide)\n";
return fake_wide<T>{b ? x.value_ : -1.f};
}
}
}
// As the Callable Object type lives in eve::tag, it's a valid namespace to put overloads in.
// We use it to parks global/generic cases overloads like masking or cross-tag ones
namespace eve::tags
{
// Use this for any elementwise
auto tag_dispatch(elementwise_callable auto const&, auto, auto x) noexcept
{
std::cout << __LINE__ << " : ELEMENTWISE\n";
return x;
}
// Use this for any scalar value in SSE2
auto tag_dispatch(tag<compute> const&, arch::sse2_, eve::scalar_value auto x) noexcept
{
std::cout << __LINE__ << " : sse2::compute(scalar)\n";
return 11*x;
}
// Use this for any scalar value in AARCH64
auto tag_dispatch(tag<compute> const&, arch::asimd_, eve::scalar_value auto x) noexcept
{
std::cout << __LINE__ << " : asimd::compute(scalar)\n";
return -10*x;
}
// Use this when masking occurs on non supported types
template<typename T, typename... Ts>
auto tag_dispatch(callable auto const& tag, arch::cpu_, bool m, T x, Ts... xs) noexcept
{
std::cout << __LINE__ << " : General masking\n";
return m ? tag_dispatch(tag, current_arch, x, xs...) : T{0};
}
template<rbr::concepts::settings S, typename... Ts>
auto tag_dispatch(callable auto const& tag, arch::cpu_, S const& opts, Ts... x)
{
using namespace rbr::literals;
std::cout << __LINE__ << " : General options handling - ";
if constexpr(S::contains("mask"_kw))
{
std::cout << " requires masking\n";
auto fn = tag[rbr::drop("mask"_kw,opts)];
return fn(opts["mask"_kw],x...);
}
else
{
std::cout << " requires nothing\n";
return tag(x...);
}
}
}
#include <iostream>
// We can use tag<Obj> to use in requirement
template<typename T>
requires requires(T x){ eve::compute(x); }
void display(const T& value)
{
std::cout << eve::compute(value) << std::endl;
}
template<typename T>
requires( !requires(T x){ eve::compute(x); } )
void display(const T& value)
{
std::cout << "Invoke: " << std::boolalpha << std::invocable<eve::tag<eve::compute>, const T&> << std::endl;
}
int main()
{
using namespace rbr::literals;
eve::fake_wide<float> x{2.5};
std::cout << "------------------------------\n";
std::cout << " 1 > " << eve::compute(x) << "\n";
display(x);
std::cout << "------------------------------\n";
std::cout << " 2 > " << eve::compute(458LL) << "\n";
std::cout << "------------------------------\n";
std::cout << " 3 > " << eve::compute[true]["pedantic"_fl](x) << "\n";
std::cout << "------------------------------\n";
std::cout << " 4 > " << eve::compute["pedantic"_fl][true](x) << "\n";
std::cout << "------------------------------\n";
std::cout << " 5 > " << eve::compute[false]["pedantic"_fl](x) << "\n";
std::cout << "------------------------------\n";
std::cout << " 6 > " << eve::compute["pedantic"_fl][false](x) << "\n";
std::cout << "------------------------------\n";
std::cout << " 7 > " << eve::compute["pedantic"_fl](x) << "\n";
std::cout << "------------------------------\n";
std::cout << " 8 > " << eve::compute[true](x) << "\n";
std::cout << "------------------------------\n";
std::cout << " 9 > " << eve::compute[false](x) << "\n";
std::cout << "------------------------------\n";
std::cout << "10 > " << eve::compute[true](45.8) << "\n";
std::cout << "------------------------------\n";
std::cout << "11 > " << eve::compute[false](45.8) << "\n";
std::cout << "------------------------------\n";
std::cout << "12 > " << eve::compute["pedantic"_fl](458LL) << "\n";
std::cout << "------------------------------\n";
display("45.8");
std::cout << "------------------------------\n";
std::cout << "> " << eve::compute << "\n";
std::cout << "------------------------------\n";
} Discussions with @DenisYaroshevskiy show we are OK we this. |
Beta Was this translation helpful? Give feedback.
-
My ideal user expirience, as a writerI would like:
Making up syntax
Note: do we maybe want to drop On Error messages:
A bit annyoing = delete. I often suffer that I get no information on why it failed. Can maybe
On tag dispatchI really don't think that everything going through tag dispatch is amazing, especially through a single one, I think it will increase overload issues. Can we at least keep default with Or it can be
or smth. I want default explicit options |
Beta Was this translation helpful? Give feedback.
-
Latest version https://godbolt.org/z/4qj5K9nzj
|
Beta Was this translation helpful? Give feedback.
-
Version 5.0
|
Beta Was this translation helpful? Give feedback.
-
PoC for default behavior |
Beta Was this translation helpful? Give feedback.
-
Revamped and implemented |
Beta Was this translation helpful? Give feedback.
-
Current status
Currently, EVE functions operating on scalar, wide and logical use a delayed ADL based overload of a named function. On top of that, a lightweight, tag dispatch system is implemented to overload functions over other types. This leads to :
Looking at more recent typical CPO+tag invoke mecanism, I think we can end up with a single, far less macro heavy system that unifies overload resolution for EVE CPO.
Proposal
The current state of the proposal is here: https://godbolt.org/z/KzvnKG7Po
Multiple things are then possible:
I think this is also somethign doable "on the side" in an incremental way by locating the CPO basic code into a separate file, add the central tag_dipatch to logical and wide and rewrigin funcion per function.
What's to discuss
Did I forgot a use case ? Can we do better than this rough POC ?
Beta Was this translation helpful? Give feedback.
All reactions