Как не делать лишний раз объявление одного и того же метода с квалификатором const? C++
Есть некий класс, который содержит метод map:
// Не нужно говорить про говнокод. Это лишь минимальный пример
// показывающий проблему
template<typename Func>
/*type*/ map(Func &&func)
{
return func(arg);
}
Функция func может принимать Arg как по const ссылке, так и нет. То есть как может изменять значение переменной-члена класса, так и не может. Из-за этого мне нужно объявить метод map дважды - с квалификатором const и без. А если я захочу, чтобы в зависимости от ref-квалификатора объект arg еще и мувился в func, то придется сделать 4 почти одинаковые версии метода map.
И если версии для l-value и r-value еще отличаются в вызове Std::move/std::forward, то версии для const и не-const не отличаются от слова вообще:
template<typename Func>
/*type*/ map(Func &&func) &
{
return func(arg);
}
template<typename Func>
/*type*/ map(Func &&func) const &
{
return func(arg);
}
Однако если оставить только метод с const-квалификатором, то при передаче аргумента Arg в func на него также навесится квалификатор const и передаваться он будет уже по константной ссылке и изменить объект arg не получится.
Вопрос: как мне избежать 4-х объявлений одного и того же метода, если использовать универсальную ссылку нельзя?
Ответы (1 шт):
Ждем C++23 с его шаблонным this.
А пока ждем, макросы спасут мир:
struct A
{
std::string arg;
QUAL_MAYBE_CONST_L_R(
template <typename F>
decltype(auto) map(F &&func) QUAL
{
return std::forward<F>(func) QUAL_IN(static_cast<decltype(arg) QUAL>(arg));
}
)
};
Идея такая: все что внутри QUAL_MAYBE_CONST_L_R дублируется 4 раза, и в каждой копии QUAL заменяется на одно из: &, const &, &&, const &&.
Из-за технических ограничений, если QUAL находится внутри круглых скобках, то перед каждой такой открывающей скобкой должно стоять QUAL_IN.
Раскрывается в:
struct A
{
std::string arg;
template <typename F>
decltype(auto) map(F &&func) &
{
return std::forward<F>(func)(static_cast<decltype(arg) &>(arg));
}
template <typename F>
decltype(auto) map(F &&func) const &
{
return std::forward<F>(func)(static_cast<decltype(arg) const &>(arg));
}
template <typename F>
decltype(auto) map(F &&func) &&
{
return std::forward<F>(func)(static_cast<decltype(arg) &&>(arg));
}
template <typename F>
decltype(auto) map(F &&func) const &&
{
return std::forward<F>(func)(static_cast<decltype(arg) const &&>(arg));
}
};
Я положил три варианта: 1. /const, 2. const &/&&, 3. Собственно, &/const &/&&/const &&.
#pragma once
/* Example usage:
* QUAL_MAYBE_CONST( QUAL int &GetX() QUAL {...} )
* This expands to:
* int &GetX() {...}
* const int &GetX() const {...}
*
* Note that if `QUAL` appears inside of parentheses, those parentheses must be preceeded by `QUAL_IN`.
* Example:
* QUAL_MAYBE_CONST( QUAL int &Foo QUAL_IN(QUAL int &x); )
* This expands to:
* int &Foo( int &x);
* const int &Foo(const int &x);
*
* We also have `QUAL_CONST_L_OR_R(...)` that gives `const &` and `&&`,
* and `QUAL_MAYBE_CONST_L_R` that gives `&`, `const &`, `&&`, and `const &&`.
*/
#define QUAL_MAYBE_CONST(...) \
IMPL_QUAL ((IMPL_QUAL_null,__VA_ARGS__)()) \
IMPL_QUAL_const((IMPL_QUAL_null,__VA_ARGS__)())
#define QUAL_CONST_L_OR_R(...) \
IMPL_QUAL_const_lref((IMPL_QUAL_null,__VA_ARGS__)()) \
IMPL_QUAL_rref ((IMPL_QUAL_null,__VA_ARGS__)())
#define QUAL_MAYBE_CONST_L_R(...) \
IMPL_QUAL_lref ((IMPL_QUAL_null,__VA_ARGS__)()) \
IMPL_QUAL_const_lref((IMPL_QUAL_null,__VA_ARGS__)()) \
IMPL_QUAL_rref ((IMPL_QUAL_null,__VA_ARGS__)()) \
IMPL_QUAL_const_rref((IMPL_QUAL_null,__VA_ARGS__)())
#define QUAL )(IMPL_QUAL_identity,
#define QUAL_IN(...) )(IMPL_QUAL_p_open,)(IMPL_QUAL_null,__VA_ARGS__)(IMPL_QUAL_p_close,)(IMPL_QUAL_null,
#define IMPL_QUAL_null(...)
#define IMPL_QUAL_identity(...) __VA_ARGS__
#define IMPL_QUAL_p_open(...) (
#define IMPL_QUAL_p_close(...) )
#define IMPL_QUAL_body(cv, m, ...) m(cv) __VA_ARGS__
#define IMPL_QUAL(seq) IMPL_QUAL_a seq
#define IMPL_QUAL_a(...) __VA_OPT__(IMPL_QUAL_body(,__VA_ARGS__) IMPL_QUAL_b)
#define IMPL_QUAL_b(...) __VA_OPT__(IMPL_QUAL_body(,__VA_ARGS__) IMPL_QUAL_a)
#define IMPL_QUAL_const(seq) IMPL_QUAL_const_a seq
#define IMPL_QUAL_const_a(...) __VA_OPT__(IMPL_QUAL_body(const,__VA_ARGS__) IMPL_QUAL_const_b)
#define IMPL_QUAL_const_b(...) __VA_OPT__(IMPL_QUAL_body(const,__VA_ARGS__) IMPL_QUAL_const_a)
#define IMPL_QUAL_lref(seq) IMPL_QUAL_lref_a seq
#define IMPL_QUAL_lref_a(...) __VA_OPT__(IMPL_QUAL_body(&,__VA_ARGS__) IMPL_QUAL_lref_b)
#define IMPL_QUAL_lref_b(...) __VA_OPT__(IMPL_QUAL_body(&,__VA_ARGS__) IMPL_QUAL_lref_a)
#define IMPL_QUAL_const_lref(seq) IMPL_QUAL_const_lref_a seq
#define IMPL_QUAL_const_lref_a(...) __VA_OPT__(IMPL_QUAL_body(const &,__VA_ARGS__) IMPL_QUAL_const_lref_b)
#define IMPL_QUAL_const_lref_b(...) __VA_OPT__(IMPL_QUAL_body(const &,__VA_ARGS__) IMPL_QUAL_const_lref_a)
#define IMPL_QUAL_rref(seq) IMPL_QUAL_rref_a seq
#define IMPL_QUAL_rref_a(...) __VA_OPT__(IMPL_QUAL_body(&&,__VA_ARGS__) IMPL_QUAL_rref_b)
#define IMPL_QUAL_rref_b(...) __VA_OPT__(IMPL_QUAL_body(&&,__VA_ARGS__) IMPL_QUAL_rref_a)
#define IMPL_QUAL_const_rref(seq) IMPL_QUAL_const_rref_a seq
#define IMPL_QUAL_const_rref_a(...) __VA_OPT__(IMPL_QUAL_body(const &&,__VA_ARGS__) IMPL_QUAL_const_rref_b)
#define IMPL_QUAL_const_rref_b(...) __VA_OPT__(IMPL_QUAL_body(const &&,__VA_ARGS__) IMPL_QUAL_const_rref_a)
Злоупотреблять этим не стоит, потому что внутри макроса теряется информация о номерах строк, и отлаживаться построчно не получится.