从函数类型中剥离所有限定符

时间:2022-02-28 17:07:52

Given a possibly varargs function type with possibly a cv-qualifier-seq and possibly a ref-qualifier, is it possible to write a type trait that strips all the qualifiers without writing 4 * 3 * 2 = 24 partial specializations?

给定一个可能的varargs函数类型,可能有cv-qualifier-seq和可能的ref-qualifier,是否可以编写一个类型特征来剥离所有限定符而不编写4 * 3 * 2 = 24个部分特化?

template<class T>
struct strip_function_qualifiers;

template<class R, class... Args>
struct strip_function_qualifiers<R(Args...)> { using type = R(Args...); };

template<class R, class... Args>
struct strip_function_qualifiers<R(Args..., ...)> { using type = R(Args..., ...); };

template<class R, class... Args>
struct strip_function_qualifiers<R(Args...) const> { using type = R(Args...); };

template<class R, class... Args>
struct strip_function_qualifiers<R(Args..., ...) const > { using type = R(Args..., ...); };

template<class R, class... Args>
struct strip_function_qualifiers<R(Args...) const &> { using type = R(Args...); };

template<class R, class... Args>
struct strip_function_qualifiers<R(Args..., ...) const & > { using type = R(Args..., ...); };

// etc. etc. for each possible combination (24 in total)

And with the new transactional memory TS adding transaction_safe to the mix, does that mean we'll need to write 48 partial specializations for this?

并且使用新的事务内存TS将transaction_safe添加到组合中,这是否意味着我们需要为此编写48个部分特化?


Edit: Quoting the description of these weird function types ([dcl.fct]/p6, quoting N4140):

编辑:引用这些奇怪的函数类型的描述([dcl.fct] / p6,引用N4140):

A function type with a cv-qualifier-seq or a ref-qualifier (including a type named by typedef-name (7.1.3, 14.1)) shall appear only as:

具有cv-qualifier-seq或ref-qualifier(包括由typedef-name(7.1.3,14.1)命名的类型)的函数类型应仅显示为:

  • the function type for a non-static member function,
  • 非静态成员函数的函数类型,

  • the function type to which a pointer to member refers,
  • 指向成员的指针引用的函数类型,

  • the top-level function type of a function typedef declaration or alias-declaration,
  • 函数typedef声明或别名声明的*函数类型,

  • the type-id in the default argument of a type-parameter (14.1), or
  • type-parameter(14.1)的默认参数中的type-id,或

  • the type-id of a template-argument for a type-parameter (14.3.1).
  • type-parameter(14.3.1)的template-argument的type-id。

[ Example:

typedef int FIC(int) const;
FIC f; // ill-formed: does not declare a member function
struct S {
   FIC f; // OK
};
FIC S::*pm = &S::f; // OK

end example ]

- 结束例子]

The effect of a cv-qualifier-seq in a function declarator is not the same as adding cv-qualification on top of the function type. In the latter case, the cv-qualifiers are ignored. [Note: a function type that has a cv-qualifier-seq is not a cv-qualified type; there are no cv-qualified function types. — end note ] [ Example:

cv-qualifier-seq在函数声明符中的作用与在函数类型之上添加cv-qualification不同。在后一种情况下,忽略cv限定符。 [注意:具有cv-qualifier-seq的函数类型不是cv限定类型;没有cv限定的函数类型。 - 尾注] [示例:

 typedef void F();
 struct S {
    const F f; // OK: equivalent to: void f();
 };

end example ]

- 结束例子]

The return type, the parameter-type-list, the ref-qualifier, and the cv-qualifier-seq, but not the default arguments (8.3.6) or the exception specification (15.4), are part of the function type. [Note: Function types are checked during the assignments and initializations of pointers to functions, references to functions, and pointers to member functions. — end note ]

返回类型,参数类型列表,ref限定符和cv-qualifier-seq,但不是默认参数(8.3.6)或异常规范(15.4),是函数类型的一部分。 [注意:在指向函数的指针和函数的引用以及指向成员函数的指针的赋值和初始化期间,会检查函数类型。 - 结束说明]

1 个解决方案

#1


8  

I see no way around this - define it once and reuse it whenever possible.
Such a huge amount of specializations is avoidable when the qualifiers are top-level - we could use std::remove_cv or std::remove_reference in that case, removing all orthogonal qualifiers in each step. Unfortunately this is not applicable for functions as explained in the paragraph quoted by you: The e.g. cv-qualifier is part of the function type, and not top-level. void() const is a fundamentally different type than void(), and thus the both have to be matched by two different partial specializations.

我认为没办法 - 定义一次并尽可能重复使用它。当限定符为*时,可以避免如此大量的特化 - 在这种情况下我们可以使用std :: remove_cv或std :: remove_reference,在每个步骤中删除所有正交限定符。遗憾的是,这不适用于您引用的段落中所述的功能: cv-qualifier是函数类型的一部分,而不是*。 void()const是一个与void()根本不同的类型,因此两者必须由两个不同的部分特化匹配。

You can shorten all specializations using macros though:

您可以使用宏缩短所有特化:

#define REM_CTOR(...) __VA_ARGS__

#define SPEC(var, cv, ref) \
template <typename R, typename... Args> \
struct strip_function_qualifiers<R(Args... REM_CTOR var) cv ref > \
{using type = R(Args... REM_CTOR var);};

#define REF(var, cv) SPEC(var, cv,) SPEC(var, cv, &) SPEC(var, cv, &&)

#define CV(var) REF(var,) REF(var, const) \
                REF(var, volatile) REF(var, const volatile)

template <typename> struct strip_function_qualifiers;

CV(()) CV((,...))

Demo.
Boost.PP is possible as well:

演示。 Boost.PP也是可能的:

#include <boost/preprocessor/tuple/enum.hpp>
#include <boost/preprocessor/seq/elem.hpp>
#include <boost/preprocessor/seq/for_each_product.hpp>

#define REF  (&&)(&)()
#define CV   (const volatile)(const)(volatile)()
#define VAR  (())((,...)) // Had to add a comma here and use rem_ctor below,
                          // otherwise Clang complains about ambiguous ellipses

#define SPEC(r, product) \
template <typename R, typename... Args> \
struct strip_function_qualifiers<R(Args... BOOST_PP_TUPLE_ENUM( \
    BOOST_PP_SEQ_ELEM(0, product))) \
    BOOST_PP_SEQ_ELEM(1, product)   \
    BOOST_PP_SEQ_ELEM(2, product)>  \
{using type = R(Args... BOOST_PP_TUPLE_ENUM(BOOST_PP_SEQ_ELEM(0, product)));};

template <typename> struct strip_function_qualifiers;

BOOST_PP_SEQ_FOR_EACH_PRODUCT(SPEC, (VAR)(CV)(REF))

Demo. Both methods won't get much longer when adding new qualifiers such as transaction_safe or transaction_safe_noinherit.

演示。添加新限定符(例如transaction_safe或transaction_safe_noinherit)时,这两种方法都不会长得多。


Here is a modified SPEC that also defines certain trait members.

这是一个修改过的SPEC,它也定义了某些特征成员。

#include <type_traits>

#include <boost/preprocessor/tuple/size.hpp>

// […]

#define SPEC(r, product)                                         \
template <typename R, typename... Args>                          \
struct strip_function_qualifiers<R(Args... BOOST_PP_TUPLE_ENUM(  \
    BOOST_PP_SEQ_ELEM(0, product))) \
    BOOST_PP_SEQ_ELEM(1, product)   \
    BOOST_PP_SEQ_ELEM(2, product)>  \
{                                     \
    using type = R(Args... BOOST_PP_TUPLE_ENUM(BOOST_PP_SEQ_ELEM(0, product))); \
                                                            \
private:                                                    \
    using cv_type = int BOOST_PP_SEQ_ELEM(1, product);      \
    using ref_type = int BOOST_PP_SEQ_ELEM(2, product);     \
public:                                                     \
    using is_const    = std::is_const<cv_type>;             \
    using is_volatile = std::is_volatile<cv_type>;          \
    using is_ref_qualified = std::is_reference<ref_type>;               \
    using is_lvalue_ref_qualified = std::is_lvalue_reference<ref_type>; \
    using is_rvalue_ref_qualified = std::is_rvalue_reference<ref_type>; \
    using is_variadic = std::integral_constant<bool,                          \
                       !!BOOST_PP_TUPLE_SIZE(BOOST_PP_SEQ_ELEM(0, product))>; \
};

#1


8  

I see no way around this - define it once and reuse it whenever possible.
Such a huge amount of specializations is avoidable when the qualifiers are top-level - we could use std::remove_cv or std::remove_reference in that case, removing all orthogonal qualifiers in each step. Unfortunately this is not applicable for functions as explained in the paragraph quoted by you: The e.g. cv-qualifier is part of the function type, and not top-level. void() const is a fundamentally different type than void(), and thus the both have to be matched by two different partial specializations.

我认为没办法 - 定义一次并尽可能重复使用它。当限定符为*时,可以避免如此大量的特化 - 在这种情况下我们可以使用std :: remove_cv或std :: remove_reference,在每个步骤中删除所有正交限定符。遗憾的是,这不适用于您引用的段落中所述的功能: cv-qualifier是函数类型的一部分,而不是*。 void()const是一个与void()根本不同的类型,因此两者必须由两个不同的部分特化匹配。

You can shorten all specializations using macros though:

您可以使用宏缩短所有特化:

#define REM_CTOR(...) __VA_ARGS__

#define SPEC(var, cv, ref) \
template <typename R, typename... Args> \
struct strip_function_qualifiers<R(Args... REM_CTOR var) cv ref > \
{using type = R(Args... REM_CTOR var);};

#define REF(var, cv) SPEC(var, cv,) SPEC(var, cv, &) SPEC(var, cv, &&)

#define CV(var) REF(var,) REF(var, const) \
                REF(var, volatile) REF(var, const volatile)

template <typename> struct strip_function_qualifiers;

CV(()) CV((,...))

Demo.
Boost.PP is possible as well:

演示。 Boost.PP也是可能的:

#include <boost/preprocessor/tuple/enum.hpp>
#include <boost/preprocessor/seq/elem.hpp>
#include <boost/preprocessor/seq/for_each_product.hpp>

#define REF  (&&)(&)()
#define CV   (const volatile)(const)(volatile)()
#define VAR  (())((,...)) // Had to add a comma here and use rem_ctor below,
                          // otherwise Clang complains about ambiguous ellipses

#define SPEC(r, product) \
template <typename R, typename... Args> \
struct strip_function_qualifiers<R(Args... BOOST_PP_TUPLE_ENUM( \
    BOOST_PP_SEQ_ELEM(0, product))) \
    BOOST_PP_SEQ_ELEM(1, product)   \
    BOOST_PP_SEQ_ELEM(2, product)>  \
{using type = R(Args... BOOST_PP_TUPLE_ENUM(BOOST_PP_SEQ_ELEM(0, product)));};

template <typename> struct strip_function_qualifiers;

BOOST_PP_SEQ_FOR_EACH_PRODUCT(SPEC, (VAR)(CV)(REF))

Demo. Both methods won't get much longer when adding new qualifiers such as transaction_safe or transaction_safe_noinherit.

演示。添加新限定符(例如transaction_safe或transaction_safe_noinherit)时,这两种方法都不会长得多。


Here is a modified SPEC that also defines certain trait members.

这是一个修改过的SPEC,它也定义了某些特征成员。

#include <type_traits>

#include <boost/preprocessor/tuple/size.hpp>

// […]

#define SPEC(r, product)                                         \
template <typename R, typename... Args>                          \
struct strip_function_qualifiers<R(Args... BOOST_PP_TUPLE_ENUM(  \
    BOOST_PP_SEQ_ELEM(0, product))) \
    BOOST_PP_SEQ_ELEM(1, product)   \
    BOOST_PP_SEQ_ELEM(2, product)>  \
{                                     \
    using type = R(Args... BOOST_PP_TUPLE_ENUM(BOOST_PP_SEQ_ELEM(0, product))); \
                                                            \
private:                                                    \
    using cv_type = int BOOST_PP_SEQ_ELEM(1, product);      \
    using ref_type = int BOOST_PP_SEQ_ELEM(2, product);     \
public:                                                     \
    using is_const    = std::is_const<cv_type>;             \
    using is_volatile = std::is_volatile<cv_type>;          \
    using is_ref_qualified = std::is_reference<ref_type>;               \
    using is_lvalue_ref_qualified = std::is_lvalue_reference<ref_type>; \
    using is_rvalue_ref_qualified = std::is_rvalue_reference<ref_type>; \
    using is_variadic = std::integral_constant<bool,                          \
                       !!BOOST_PP_TUPLE_SIZE(BOOST_PP_SEQ_ELEM(0, product))>; \
};