LINUX.ORG.RU

SFINAE по количеству аргументов у функции

 , , ,


0

3

Можете ржать, но я не осилил запилить SFINAE.

Темка такая. Мой код работает с некой библиотекой. В одной из версий у функции из этой библиотеки изменилось число аргументов. Было 3, а стало 4. Нужно написать код, который сможет работать с обеими версиями. Мне лень обмазывать код дефайнами и вносить изменения в сборочный скрипт.

Я хочу, чтобы шаблонная магия проверила сколько у функции аргументов и в зависимости от этого подставила нужное количество параметров. Накидал минимальный пример. Там у foo два аргумента, но компилятор залезает в первую шаблонную функцию, где передаётся три аргумента и умирает. Надо как-то отучить его туда ходить.

Стандарт C++14.

★★★

Ах да,

Основные правила: 1) всё что не зависит от неизвестных - генерирует ошибку. 2) перегрузка должна точно определяться по сигнатуре

Угадай, какое правило ты нарушил.

RibiKukkan ()

Ничего не понял, а враппер с дефолтными аргументами-флажками не подойдет (возможно std::optional c++17)?

И вообще в cdecl очищением стека занимается вызывающий код, так что можно сделать хак, возможно даже легальными языковыми средствами.

anonymous ()

А может быть проще сделать wrapper, который обеспечит возможность использования единого API для функций из этих библиотек?
ИМХНО это не сложно и реализуется быстро.

Владимир

anonymous ()
template<int N>
static int setupTempl(int a, int b)
{
     static_assert(N == N, "invalid N");
     return 0;
}

template<>
static int setupTempl<2>(int a, int b)
{
     return foo(a, b);
}
 
template<>
static int setupTempl<3>(int a, int b)
{
     return foo(a, b, true);
}

Пишу с телефона, лень чекать.

Kuzy ★★★ ()
Последнее исправление: Kuzy (всего исправлений: 1)
Ответ на: комментарий от DELIRIUM

обычная перегрузка

Как? У меня в зависимости от версии библиотеки меняется число аргументов. Если у меня где-то в коде будет написан вызов функции с четырьмя аргументами, компиляция завалится на младшей версии библиотеки.

Я понимаю, что это жуткий костыль и правильнее будет сделать ifdef по версии библиотеки. Но пока я хочу поговнокодить.

ox55ff ★★★ ()
Ответ на: комментарий от ox55ff

Правило один. Компилятор валидирует всё то, что можно что можно валидировать. Вызов функции у тебя никак не зависит от параметров шаблона, а значит везде будет таким же и его можно валидировать, что и делает компилятор.

RibiKukkan ()

Например:

#include <type_traits>
#include <iostream>

int foo(int a, int b)
{
     return 1;
}

//int foo(int a, int b, bool c)
//{
//    return 0;
//}

template <typename R, typename ... Types>
constexpr size_t getArgumentCount(R(*)(Types ...))
{
    return sizeof...(Types);
}

template<typename T, int N = getArgumentCount(T())>
static int setupTempl(int a, int b, T foo, typename std::enable_if<N == 3, void>::type* = nullptr)
{
     return foo(a, b, true);
}

template<typename T, int N = getArgumentCount(T())>
static int setupTempl(int a, int b, T foo, typename std::enable_if<N == 2, void>::type* = nullptr)
{
     return foo(a, b);
}

int main()
{
     setupTempl(1, 2, foo);

     return 0;
}
xaizek ★★★★★ ()

Если такая функция всего одна или их немного, то можно обойтись совсем без шаблонов: https://wandbox.org/permlink/Q6hGohx6orJYBirj

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

eao197 ★★★★★ ()
Ответ на: комментарий от rumgot

typename std::enable_if<N == 3, void>::type* = nullptr

Тут написано говно. Во-первых enable_if по умолчанию возвращает void и ненужно его указывать.

Во-вторых, тут предпринимается попытка сделать enable_if частью декларации. enable_if возвращает void - далее к нему добавляет * и получается аргумент void * = nullptr.

Очевидно, что писать тут enable_if не имеет смысл, т.к. его можно написать вместо типа возврата. Писать подобным образом его имеет смысл тогда, когда у тебя тип возврата auto.

typename std::enable_if<N == 3, void>::type = 0

Это не соберётся, нужно заменить void на какой-нибудь int. Будет int = 0; void = 0 не соберётся.

RibiKukkan ()
Ответ на: комментарий от rumgot

Это хуже чем указатель и nullptr ?

Я уже ответил. Автор просто не знает как использовать enable_if. Он думал, что туда можно написать только void. И поэтому попытался из void сделать сделать правильную декларацию. Никакой разницы нет.

typename std::enable_if<N == 3>::type * = nullptr - даже такой вариант сложнее варианта с интом.

RibiKukkan ()
Ответ на: комментарий от rumgot

Как по мне, так никакой, чистой воды вкусовщина.

Хотя сам по себе подход с левым аргументом не очень хорош, есть особенности, которые даже на cppreference прямо описаны: https://en.cppreference.com/w/cpp/types/enable_if (см. раздел Notes).

eao197 ★★★★★ ()
Ответ на: комментарий от RibiKukkan

ox55ff
Или так для C++11 / C++14 :

#include <iostream>
#include <type_traits>

// int foo(int a, int b) { return 1; }

int foo(int a, int b, bool c) { return 0; }

template <int (*f)(int, int, bool)>
static auto setupTempl(int a, int b) -> decltype(f(a, b, true)) {
  return f(a, b, true);
}

template <int (*f)(int, int)>
static auto setupTempl(int a, int b) -> decltype(f(a, b)) {
  return f(a, b);
}

int main() {
  setupTempl<foo>(1, 2);
  return 0;
}

rumgot ★★★★★ ()
Последнее исправление: rumgot (всего исправлений: 2)
Ответ на: комментарий от RibiKukkan

Или немного проще, т.к. везде возвращается int:

#include <iostream>
#include <type_traits>

int foo(int a, int b) { return 1; }

// int foo(int a, int b, bool c) { return 0; }

template <int (*f)(int, int, bool)>
static int setupTempl(int a, int b) {
  return f(a, b, true);
}

template <int (*f)(int, int)>
static int setupTempl(int a, int b) {
  return f(a, b);
}

int main() {

  setupTempl<foo>(1, 2);
  return 0;
}

rumgot ★★★★★ ()
Ответ на: комментарий от RibiKukkan

Ага, еще напишу вариант, когда существуют и доступны сразу две версии функции foo (но только для C++17, т.к. до этого ограничение на нетиповые параметры шаблонов):

int foo(int a, int b) { return 1; }
int foo(int a, int b, bool c) { return 0; }

template <int (*f)(int, int, bool)> static int setupTempl(int a, int b) {
  return f(a, b, true);
}

template <int (*f)(int, int)> static int setupTempl(int a, int b) {
  return f(a, b);
}

int main() {
  constexpr int (*pFooInt)(int, int) = foo;
  setupTempl<pFooInt>(1, 2);

  constexpr int (*pFooBool)(int, int, bool) = foo;
  setupTempl<pFooBool>(1, 2);

  setupTempl<(int (*)(int, int))foo>(1, 2);
  setupTempl<(int (*)(int, int, bool))foo>(1, 2);

  setupTempl<static_cast<int (*)(int, int)>(foo)>(1, 2);
  setupTempl<static_cast<int (*)(int, int, bool)>(foo)>(1, 2);

  return 0;
}

rumgot ★★★★★ ()
Ответ на: комментарий от eao197

Согласен. Так тоже работает:

int foo(int a, int b) { return 1; }

// int foo(int a, int b, bool c) { return 0; }

template <int (*f)(int, int, bool)>
static auto setupTempl(int a, int b) {
  return f(a, b, true);
}

template <int (*f)(int, int)>
static auto setupTempl(int a, int b) {
  return f(a, b);
}

int main() {

  setupTempl<foo>(1, 2);
  return 0;
}

rumgot ★★★★★ ()
Ответ на: комментарий от anonymous

Ты наверное хотел написать

Да.

Но так не можно делать. UB.

Уверен?

Я - нет, и что-то вспоминаю, что лет 5 назад листал стандарт на static_assert(!std::is_same<T, T>::value, "invalid type") и вроде не UB.

Лень проверять, честно говоря.

Kuzy ★★★ ()
Ответ на: комментарий от Kuzy
 Но так не можно делать. UB.

Уверен?

Строго говоря, ill-formed, no diagnostic requires. Но т.к. никакие реализации не диагностируют, получается молча скомпилированная ill-formed программа. Стандарт C++, естественно, не определяет поведение того, что формально даже не является программой на C++.

Я - нет, и что-то вспоминаю, что лет 5 назад листал стандарт

Раз 5 лет назад, то вот из C++14:

[temp.res]/8

If no valid specialization can be generated for a template, and that template is not instantiated, the template is ill-formed, no diagnostic required.

anonymous ()
Ответ на: комментарий от Kuzy

static_assert(!std::is_same<T, T>::value, "invalid type")

С этим сложнее. std::is_same<T, T> как будто бы можно явно специализировать для какого-то T так, чтобы value было равно false, так что со стороны [temp.res]/8 проблем не будет: для этого T есть валидная специализация темплета, содержащего static_assert. Но проблема в том, что std::is_same<T, T> на самом деле запрещено специализировать.

anonymous ()
Ответ на: комментарий от anonymous

Угу, нашел уже.

Не очевидно, на самом деле, что имеется ввиду. Валидная специализация, очевидно, дана.

Алсо, в новой версии это вот так.

(8) The program is ill-formed, no diagnostic required, if:
(8.1) no valid specialization can be generated for a template or a substatement of a constexpr if statement within a template and the template is not instantiated, or
(8.4) a hypothetical instantiation of a template immediately following its definition would be ill-formed due to a construct that does not depend on a template parameter, or

Тут уже очевидно что не UB из (8.4), а оно оч похоже на твою цитату.

Очевидно, нужно смотреть че у них конкретно обозначает слово template. Думаю что это вместе со специализациями.

Kuzy ★★★ ()
Ответ на: комментарий от anonymous

Эм. Там это написано в отношении темлейта в котором этот ассерт.

Конечно, очевидно, что никакой компилятор такой фигней заниматься не будет. Кроме того темплейты тьюринг полные и это в принципе невозможно проверить для всех случаев.

Kuzy ★★★ ()
Ответ на: комментарий от Kuzy

Очевидно, нужно смотреть че у них конкретно обозначает слово template. Думаю что это вместе со специализациями.

Нет. Полная специализация это не template, а частичная специализация — это отдельный template.

anonymous ()
Ответ на: комментарий от Kuzy

Эм. Там это написано в отношении темлейта в котором этот ассерт.

Я про него и говорил. Если есть шаблон-прокладка типа is_same, то вопрос с применением [temp.res]/8 ([temp.res]/(8.1)) уже сложнее. Эту прокладку можно специализировать, чтобы аргумент static_assert вычислялся в true и тогда понятно что уже у нас есть валидная специализация.

anonymous ()
Ответ на: комментарий от anonymous

Это не разрешимо в общем случае.

Вообще с такой формулировкой это UB. Да.

Получается нужно написать свой std::is_same завернуть N в std::integral_constant, а потом блевануть от того, что ты программист на C++ (регулярно так делаю).

Kuzy ★★★ ()
Ответ на: комментарий от Kuzy

Вон в последнем опросе на лоре побеждает Rust, но макросный шлак и Rust доведет до такого же состояния. Там стандартные макросы можно пихать чуть ли не через строчку и это приветствуется языком. По сути такие же кастыли из рода std::enable_if.

bhfq ★★★★★ ()
Последнее исправление: bhfq (всего исправлений: 1)
Ответ на: комментарий от Kuzy

Получается нужно написать свой std::is_same завернуть N в std::integral_constant

Ну не так всё сложно. Достаточно что-нибудь вроде template<auto> dependent_value_false : std::false_type {} и использовать в static_assert(dependent_value_false<N>::value).

anonymous ()