Устранение перегрузок шаблона с помощью типа возвращаемого параметра функции - PullRequest
1 голос
/ 28 июня 2019

Я пытаюсь обернуть несколько сторонних библиотечных функций, использующих шаблон в стиле C «Call - Allocate - Call Again» (для этого должно быть более подходящее название). Например:

int EnumerateFoo(float f, uint32_t* count, float* buf) {
    if (!buf) {
        *count = 3;
        return 0;
    }
    if (*count < 3) {
        cout << "buffer too small\n";
        return -1;
    }
    buf[0] = f;
    buf[1] = f + f;
    buf[2] = f * f;
    return 0;
}

// ...

uint32_t count = 0;
int ret = EnumerateFoo(3.14f, &count, nullptr);
if (ret) return ret;
float* buf = new float[count];
ret = EnumerateFoo(3.14f, &count, buf);
if (ret) return ret;

Я бы хотел обернуть это так, чтобы такие функции можно было вызывать гораздо более кратко. В идеале, я мог бы назвать их как, например ::1004

vector<float> vec = WrapEnumerate(EnumerateFoo, 3.14f);

Самое близкое, что я получил, это что-то вроде следующего (использование std::bind, поскольку параметры count / buffer не всегда имеют одинаковые индексы параметров):

template<class T>
vector<T> EnumToVec(function<int(uint32_t*,T*)> fn) {
    vector<T> ret;
    uint32_t count = 0;
    if(fn(&count, nullptr))
        return vector<T>();
    ret.resize(count);
    if(fn(&count, ret.data()))
        return vector<T>();
    return ret;
}

// ...

auto vec = EnumToVec<float>(bind(
    EnumerateFoo, 3.14f, placeholder::_1, placeholder::_2));

Это работает достаточно хорошо, но, к сожалению, некоторые из библиотечных функций имеют void возвращаемые типы вместо int. Я пытался создать перегрузку EnumToVec, которая заменяет function<int... на function<void..., но компилятор говорит, что вызов неоднозначен.

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

Эксперимент здесь: https://ideone.com/IDygru

1 Ответ

0 голосов
/ 28 июня 2019

Проблема в том, что значение, возвращаемое std::bind, может быть преобразовано в std::function, но не std::function.

Это своего рода проблема с яйцом и курицей.

Когда вы вызываете

EnumToVec<float>(std::bind(EnumerateFoo, 3.14f, _1, _2));

, вы получаете то, что компилятор не может выбирать между void и int версией, потому что EnumVec не получает значение std::function;и не получает std::function, потому что компилятор не может выбирать между void и int версией.

Возможное решение - явно создать правильный std::function и вызов EnumToVec

std::function<int(uint32_t *, float *)> ef { std::bind(EnumerateFoo, 3.14f, _1, _2) };
auto vecFoo = EnumToVec(ef);

Заметьте, что вы можете избежать явного указания параметра шаблона float, поскольку он может быть выведен с помощью ef.

Еще одно возможное решение - отдать std::function, получитьисполняемый файл как универсальное имя типа и SFINAE включают / отключают две функции в соответствии с типом, возвращаемым функционалом

Что-то как

template <typename T, typename F>
auto EnumToVec (F const & fn)
   -> std::enable_if_t<std::is_same_v<
         decltype(fn(std::declval<std::uint32_t*>(), std::declval<T*>())),
         int>, std::vector<T>>
 { //....^^^ int here
   vector<T> ret;
   uint32_t count = 0;
   if(fn(&count, nullptr))
      return vector<T>();
   ret.resize(count);
   if(fn(&count, ret.data()))
      return vector<T>();
   return ret;
 }


template <typename T, typename F>
auto EnumToVec (F const & fn)
   -> std::enable_if_t<std::is_same_v<
         decltype(fn(std::declval<std::uint32_t*>(), std::declval<T*>())),
         void>, std::vector<T>>
 { // ...^^^^  void here
   vector<T> ret;
   uint32_t count = 0;
   fn(&count, nullptr);
   ret.resize(count);
   fn(&count, ret.data());
   return ret;
 }

Так что вы можете вызвать

auto vecFoo = EnumToVec<float>(std::bind(EnumerateFoo, 3.14f, _1, _2));

но объясняя T имя типа, потому что не выводится.

...