2

Motivation: In the implementation of P0288 std::move_only_function, I'd like to write a non-allocating special case for conversion from move_only_function<int() noexcept> to move_only_function<int()>:

move_only_function<int() noexcept> f = []() noexcept { return 42; };
move_only_function<int()> g = std::move(f);  // should just copy the bits

I want to write, like,

if constexpr (is_noexcept_version_of<HisSignature, MySignature>::value) { ... }

I wanted to implement that type trait like this:

template<class, class>
struct is_noexcept_version_of : std::false_type {};

template<class Tp>
struct is_noexcept_version_of<Tp noexcept, Tp> : std::true_type {};

but no vendor accepts that; they all think Tp noexcept is a syntax error.

Question: How would you write this kind of type-trait without a combinatorial explosion of partial specializations, i.e. without exhaustively going through all the possible combinations of &, &&, const, etc.? Is it possible to write simple closed-form type traits for is_noexcept_v<T>, add_noexcept_t<T> and remove_noexcept_t<T>?

Quuxplusone
  • 23,928
  • 8
  • 94
  • 159
  • "*should just copy the bits*" Um... how exactly would that work? – Nicol Bolas Jul 30 '22 at 03:01
  • @NicolBolas: FYI, it ends up working [like this](https://github.com/Quuxplusone/llvm-project/commit/a7efa30bf9481999e5fff6379f68d39506a60289). And it turned out that I didn't even need this type-trait; a stronger version of user17732522's argument proves that _in any case where the implicit conversion is permitted_, we don't need any further test (and if it's _not_ permitted then we never get into the converting ctor's body anyway). (Future readers take note: this is all `move_only_function` trivia unrelated to the actual type-trait question.) – Quuxplusone Aug 03 '22 at 22:36

2 Answers2

3

Aside from qualification conversions, the only possible implicit conversions between pointer-to-function types are the ones that remove noexcept and similarly for pointers-to-member-functions (except for base-to-derived conversion), so I think the following should work

struct C {};

template<class A, class B>
struct is_noexcept_version_of : std::bool_constant<
    requires {
       requires std::is_convertible_v<A C::*, B C::*>;
       requires std::is_function_v<A>;
       requires !std::is_same_v<A, B>;
    }> {};
user17732522
  • 53,019
  • 2
  • 56
  • 105
0

And you can reduce the length of the combinatorial mess by combining stuff.

template<class F>
struct function_info;

template<class R, class T, class...Args>
struct function_info<R (T::*)(Args...) const&&>:
  function_helper< R(Args...), class_t<T>, const_v, rvalue_v >
{};
// ^^^^^ combinatorial explosion here ^^^^^
// You have to decompose the type into its independent dimensions, so
// you get 12 (24 if you want to support both methods and functions
// transparently). 

template<class Sig, class T, auto constness, auto refness, auto exceptness >
struct function_helper:
  base_properties<Sig, T, constness, refness, exceptness>
  derived_properties<Sig, T, constness, refness, exceptness>
{};
template<class Sig, class T, auto constness, auto refness, auto exceptness >
using function_helper_t = typename function_helper<Sig, T, consteness, refness, exceptness>::type;


template<class Sig, class T, auto constness, auto refness, auto exceptness>
struct derived_properties {
  using without_const = function_helper_t<Sig, T, no_const_v, refness, exceptness >;
  using with_const = function_helper_t<Sig, T, const_v, refness, exceptness >;
  // ... add/remove each property here.  An add/remove for each dimension.
  // This is the spot that this technique saves on lines
};

template<class Sig, class T, auto constness, auto refness, exceptness >
struct func_type;
// combinatorial explosion here:
template<class R, class...Args, class T>
struct func_type<R(Args...), T, const_v, rvalue_v, noexcept_v> {
  using type = R(T::*) const&& noexcept;
};
// ^^^^^ You have to rebuild the type from the dimensional values, which
// means you get 2 * 3 * 2 = 12 different copy-pastas here ^^^

template<class Sig, class T, auto constness, auto refness, auto exceptness >
struct base_properties:
  func_type<Sig, T, constness, refness, exceptness >
{
  using sig_t = Sig;
  using class_t = T;
  constexpr auto const_v = constness;
  constexpr auto ref_v = refness;
  constexpr auto except_v = exceptness;
};

this at least removes one layer in the combinatorial explosion; namely, assuming you want to do the same thing with a bunch of other types (noexcept, const, reference ness, etc).

We decompose the type in one spot, and we recompose it in another, and we can reuse those decompositions/recompositions.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524