Получите преимущества универсальной ссылки, без универсальной ссылки
проблема
Давайте предположим, что функция func
который принимает любой контейнер в форме Container<Type, N, Args...>
(это контейнер, который принимает в качестве первого аргумента шаблона тип, а в качестве второго std::size_t
определяет, сколько аргументов в контейнере) и возвращает его i
й элемент тогда и только тогда, когда N
находится между 40
а также 42
,
Примером такого контейнера является std::array
,
Моя первая версия функции будет выглядеть следующим образом:
template
< template<class, std::size_t, class...> class Container
, class Type
, std::size_t N
, class... Args >
auto func(std::size_t i, Container<Type, N, Args...>& container) -> decltype(container[0]) {
static_assert(N >= 40 && N <= 42, "bla bla bla");
return container[i];
}
и тогда мне понадобится const
перегрузка:
template
< template<class, std::size_t, class...> class Container
, class Type
, std::size_t N
, class... Args >
auto func(std::size_t i, const Container<Type, N, Args...>& container) -> decltype(container[0]) {
static_assert(N >= 40 && N <= 42, "bla bla bla");
return container[i];
}
Вопрос
Можно ли определить что-то вроде (это не будет работать, потому что это не универсальная ссылка):
template
< template<class, std::size_t, class...> class Container
, class Type
, std::size_t N
, class... Args >
auto func(std::size_t i, Container<Type, N, Args...>&& container) -> decltype(container[0]) {
// ^^
static_assert(N >= 40 && N <= 42, "bla bla bla");
return container[i];
}
для того, чтобы определить одну версию этой функции и сделать это работа для обоих Container<Type, N, Args...>&
а также const Container<Type, N, Args...>&
?
6 ответов
Вы не можете получить преимущества "универсальных ссылок" без фактического использования универсальных ссылок, поэтому просто сделайте Container
быть параметром "универсальной ссылки". Если вы делаете это, все, что вам нужно сделать, это использовать альтернативную технику, чтобы найти N
,
Одним из вариантов является просто сделать Container
хранить N
в static
переменная (или в typedef
d" std::integral_constant
или constexpr
функция). Другой вариант - написать новую (мета) функцию, единственная цель которой - найти N
, Я предпочел бы первый вариант, но я напишу второй вариант в ответе, так как он менее навязчив (не требует каких-либо изменений в Container
).
//This can alternatively be written as a trait struct.
template
< template<class, std::size_t, class...> class Container
, class Type
, std::size_t N
, class... Args >
constexpr std::size_t get_N(Container<Type, N, Args...> const&) { return N; }
template <class Container>
auto func(std::size_t i, Container &&container) -> decltype(container[i]) {
//alternatively, use std::tuple_size or container.size() or whatever
constexpr std::size_t N = get_N(container);
static_assert(N >= 40 && N <= 42, "bla bla bla");
return container[i];
}
Теперь вам нужна возможность пересылки container[i]
с категорией ценностей и ценностей container
, Для этого используйте вспомогательную функцию, которая является обобщением std::forward
, Это действительно ужасно, так как в стандартной библиотеке нет такой поддержки (к счастью, вам нужно написать это только один раз, и это полезно для решения целого ряда различных проблем). Сначала тип расчетов:
template<typename Prototype, typename T_value, typename T_decayed>
using forward_Const_t =
typename std::conditional<
std::is_const<Prototype>::value || std::is_const<T_value>::value,
T_decayed const,
T_decayed
>::type;
template<typename Prototype, typename T_value, typename T_decayed>
using forward_CV_t =
typename std::conditional<
std::is_volatile<Prototype>::value || std::is_volatile<T_value>::value,
forward_Const_t<Prototype, T_value, T_decayed> volatile,
forward_Const_t<Prototype, T_value, T_decayed>
>::type;
template<typename Prototype, typename T>
struct forward_asT {
static_assert(
std::is_reference<Prototype>::value,
"When forwarding, we only want to be casting, not creating new objects.");
static_assert(
!(std::is_lvalue_reference<Prototype>::value &&
std::is_rvalue_reference<T>::value),
"Casting an rvalue into an lvalue reference is dangerous");
typedef typename std::remove_reference<Prototype>::type Prototype_value_t;
typedef typename std::decay<T>::type T_decayed;
typedef typename std::remove_reference<T>::type T_value;
typedef typename std::conditional<
std::is_lvalue_reference<Prototype>::value,
forward_CV_t<Prototype_value_t, T_value, T_decayed> &,
forward_CV_t<Prototype_value_t, T_value, T_decayed> &&>::type type;
};
template<typename Prototype, typename T>
using forward_asT_t = typename forward_asT<Prototype,T>::type;
Теперь функция:
//Forwards `val` with the cv qualification and value category of `Prototype`
template<typename Prototype, typename T>
constexpr auto forward_as(T &&val) -> forward_asT_t<Prototype, T &&> {
return static_cast<forward_asT_t<Prototype, T &&>>(val);
}
Теперь, когда определены вспомогательные функции, мы можем просто написать func
как:
template <typename Container>
auto func(std::size_t i, Container &&container) ->
decltype(forward_as<Container &&>(container[i]))
{
constexpr std::size_t N = get_N(container);
static_assert(N >= 40 && N <= 42, "bla bla bla");
return forward_as<Container &&>(container[i]);
}
Я не думаю, что вы можете воспользоваться преимуществами специальных правил удержания для универсальных ссылок без их использования. Обходной путь несколько прост - используйте универсальную ссылку и класс черты, чтобы соответствовать шаблону и извлечь N
:
template<class T> struct matched : std::false_type { };
template< template<class, std::size_t, class...> class Container
, class Type
, std::size_t N
, class... Args >
struct matched<Container<Type, N, Args...>> : std::true_type {
constexpr static std::size_t size = N;
};
template
< class Container, typename=std::enable_if_t<matched<std::decay_t<Container>>::value> >
auto func(std::size_t i, Container&& container) -> decltype(container[0]) {
static_assert(matched<std::decay_t<Container>>::size >= 40 && matched<std::decay_t<Container>>::size <= 42, "bla bla bla");
return container[i];
}
Попробуйте что-то вроде этого:
template<typename U, typename T> struct F;
template<template<class, std::size_t, class...> class Container
, class Type
, std::size_t N
, typename T
, class... Args> struct F<Container<Type, N, Args...>, T> {
static auto func(std::size_t i, T&& t) {
static_assert(N >= 40 && N <= 42, "bla bla bla");
return t[i];
}
}
template<typename U> auto func(std::size_t i, U&& container) {
return F<std::decay<U>::type, U>::func(i, container);
}
Не совсем уверен, что это того стоило.
Всякий раз, когда вы видите такой вопрос, подумайте SFINAE. Затем подумайте: "Нет, это плохая идея, должен быть другой путь". Обычно такой способ включает диспетчеризацию тегов.
Можем ли мы использовать диспетчеризацию тегов? Да мы можем
template<class...> struct types{using type=types;};
// in case your C++ library lacks it:
template<class T>using decay_t=typename std::decay<T>::type;
template
< template<class, std::size_t, class...> class Container
, class Type
, std::size_t N
, class... Args
, class Container
>
auto func_internal(
std::size_t i,
types<Container<Type, N, Args...>>,
Container&& container
) -> decltype(container[0]) {
static_assert(N >= 40 && N <= 42, "bla bla bla");
return container[i];
}
template<class Container>
auto func( std::size_t i, Container&& container )
-> func_internal( i, types<decay_t<Container>>{}, std::declval<Container>() )
{
return func_internal( i, types<decay_t<Container>>{}, std::forward<Container>(container) );
}
и мы взяли func
, обернул информацию о типе в types<?>
тег, передал его func_internal
который извлекает всю вкусную информацию подтипа из types<?>
тег и прямое состояние от Container&&
,
Тело вашего func
просто мигрирует в func_internal
, и если вы получите ошибку с неправильным типом, ошибка будет types<blah>
не совпадает types<Container<Type, N, Args...>>
Это не плохая ошибка.
Вы также можете объединить несколько таких совпадений в один параметр.
Я вижу только одно решение:
template<
template<typename , std::size_t, class...> class ContainerType
, typename Type
, std::size_t N
, class... Args
>
void test(const ContainerType<Type, N, Args...>&){
static_assert(N >= 40 && N <= 42, "bla bla bla");
}
template
<typename ContainerType> // you need to be less specific in your container type declaration here to allow compiler deduce const ContainerType&& and ContainerType&& for you
auto func(std::size_t i, ContainerType&& container) -> decltype(container[0]) {
test(container); // compiler will throw it out because only static check is here.
return container[i];
}
Я считаю, что самое близкое, что вы можете получить в C++11, это что-то вроде этого:
template<class Container>
struct container_traits{};
template<
template<class, std::size_t, class...> class Container,
class Type,
std::size_t N,
class... Args>
struct container_traits< Container<Type, N, Args ... > >
{
typedef Type type;
enum {size = N};
};
template<class Container,
unsigned N = container_traits<
typename std::remove_reference<Container>::type >::size>
auto func(std::size_t i, Container && container) -> decltype(container[0])
{
static_assert(N >= 40 && N <= 42, "bla bla bla");
return container[i];
}
Пример:
std::array<int,41> a;
func(41,a); // Ok, pased by lvalue ref.
// "static assert, bla bla bla" (would be passed by rvalue-ref)
func(1, std::array<int,2>{});
// Error, no suitable overload overload, the only func available fails with SFINAE
func(15, int{});