шаблоны с переменным числом аргументов такое же количество аргументов функции, как и в классе

Как определить сигнатуру метода, чтобы он принимал то же количество аргументов, что и определение класса вариативного шаблона? Например, как определить класс Array:

template<typename T, int... shape>
class Array
{
public:
    T& operator () (???);
};

Таким образом, вы сможете называть это так:

Array<int, 3, 4, 5> a;
a(1, 2, 3) = 2;

person DikobrAz    schedule 05.11.2014    source источник
comment
Я думаю, это должно сработать: T& operator()(decltype(shape)... args);   -  person 0x499602D2    schedule 05.11.2014
comment
@ 0x499602D2 Что обобщается до: T& operator()(decltype(shape, X())... args); для изменения типа.   -  person Deduplicator    schedule 05.11.2014
comment
@Deduplicator Действительно. Но я предположил, что он хотел int.   -  person 0x499602D2    schedule 05.11.2014
comment
Отлично, все подходы работают, спасибо всем!   -  person DikobrAz    schedule 05.11.2014
comment
Если вы просто ищете многомерную версию std::array, вы можете сэкономить время с помощью трюк с псевдонимом вариативного массива.   -  person Casey    schedule 06.11.2014
comment
@Casey, отличный трюк, но контекст, в котором я работаю, немного отличается. Я думал, что массив - это просто чистый пример.   -  person DikobrAz    schedule 06.11.2014


Ответы (2)


template<class T, int...Shape>
class Array {
  template<int>using index_t=int; // can change this
public:
  T& operator()(index_t<Shape>... is);
};

or:

template<class T, int...Shape>
class Array {
public:
  T& operator()(decltype(Shape)... is);
};

or:

template<class T, int...Shape>
class Array {
public:
  T& operator()(decltype(Shape, int())... is);
};

если вы хотите иметь возможность изменить тип параметра, чтобы он отличался от Shape.

Я считаю, что decltype сложнее понять прикосновение, чем using, особенно если вы хотите изменить тип параметра, чтобы он отличался от int.

Другой подход:

template<class T, int...Shape>
class Array {
public:
  template<class...Args,class=typename std::enable_if<sizeof...(Args)==sizeof...(Shape)>::type>
  T& operator()(Args&&... is);
};

который использует SFINAE. Однако это не гарантирует, что Args являются целочисленными типами. Мы могли бы добавить еще один пункт, если бы захотели (скажем, все Args можно преобразовать в int).

Еще один подход состоит в том, чтобы ваш operator() принимал пакет значений, например std::array<sizeof...(Shape), int>. Звонящие должны были:

Array<double, 3,2,1> arr;
arr({0,0,0});

используйте набор {}s.

Окончательный подход будет следующим:

template<class T, int...Shape>
class Array {
public:
  template<class...Args>
  auto operator()(Args&&... is) {
    static_assert( sizeof...(Args)==sizeof...(Shapes), "wrong number of array indexes" );
  }
};

где мы принимаем что-либо, затем генерируем ошибки, если это неправильное количество аргументов. Это генерирует очень чистые ошибки, но не выполняет правильную перегрузку оператора SFINAE.

Я бы порекомендовал диспетчеризацию тегов, но я не вижу способа сделать ее намного чище, чем решение SFINAE, с дополнительными decltype и всеми остальными или лучшими сообщениями об ошибках, чем версия static_assert, с другой стороны.

person Yakk - Adam Nevraumont    schedule 05.11.2014
comment
Кажется, проверка на преобразование int лишняя, Array‹int, 1, 2, 3› a; а((символ)1, (короткий)2, 2.0f); все равно работает. - person DikobrAz; 05.11.2014
comment
@DikobrAz страх будет Array<int, 1,2,3> a; a( "hello", "world", 3 ); тоже сработает, а затем сломается в теле operator(), что далеко от идеала. Если у вас есть другой код метапрограммирования, который спрашивает, можете ли вы быть вызваны с этими аргументами, вы бы предпочли не лгать об этом. - person Yakk - Adam Nevraumont; 05.11.2014
comment
Не следует пренебрегать тем, что каждый бит, который не нужно предполагать, искать или все еще помнить, означает, что гораздо больше мозговой мощности доступно для других вещей. Что заставляет меня предпочитать decltype. (Хотя это не так уж и плохо, когда определение дополнительного типа находится прямо над местом его использования.) - person Deduplicator; 05.11.2014
comment
@Yakk Я понимаю, я ошибочно сравнил его с первоначальным подходом с decltype. - person DikobrAz; 05.11.2014
comment
Я бы сказал, что типы индексов должны совпадать с типами экстентов, что делает второй вариант правильным. - person Casey; 06.11.2014
comment
Кстати, кажется, что в gcc 4.6 работает только первый подход, goo.gl/e7CNBC, goo.gl/JHZO0n - если вы не используете псевдоним шаблона, а typedef как в stackoverflow.com/a/26766667/1008902 - person DikobrAz; 06.11.2014
comment
Есть ли шанс скомпилировать это с помощью Visual Studio? Кажется, работает только подход SFINAE. - person DikobrAz; 29.12.2014
comment
@diko вы протестировали все 7 упомянутых подходов, и только один работает в VS2015? Что ж, это кажется правдоподобным. - person Yakk - Adam Nevraumont; 29.12.2014

Я предполагаю, что вы хотите, чтобы ваши аргументы были одного типа, возможно, используя целочисленный тип (я просто буду использовать int). Простой подход — использовать уже имеющийся пакет параметров:

template <int>
struct shape_helper { typedef int type; };

template <typename T, int... Shape>
class Array
{
public:
    T& operator()(typename shape_helper<Shape>::type...);
}; 
person Dietmar Kühl    schedule 05.11.2014
comment
Почему-то я предпочитаю не вводить дополнительные типы. Как прокомментировано здесь: stackoverflow.com/questions/26766617/ - person Deduplicator; 05.11.2014
comment
@ 0x499602D2: да, тип был введен намеренно. - person Dietmar Kühl; 05.11.2014
comment
@Deduplicator: да, использование decltype(shape, X())... - ловкий трюк. - person Dietmar Kühl; 05.11.2014