Попробуйте сделать мета-функцию, которая находит метод "size" в классе - PullRequest
0 голосов
/ 16 ноября 2018

Я пытаюсь написать функцию getSize (), которая принимает некоторый аргумент шаблона, пытается найти метод или поле в этом аргументе и возвращает size () или size.

мой код:

#include <iostream>
#include <vector>
#include <utility>
#include <string>
#include <type_traits>


template <typename T>
class has_size {
private:
  typedef char Yes;
  typedef Yes No[2];

  template <typename U, U> struct really_has;

  template<typename C> static Yes& Test(really_has <size_t (C::*)() const,     &C::size>*);
  template<typename C> static Yes& Test(really_has <size_t (C::*)(), &C::size>*);

  template<typename> static No& Test(...);

public:
    static bool const value = sizeof(Test<T>(0)) == sizeof(Yes);
};

template <class T>
size_t get_size(T t){

    size_t res = 0;
    if(has_size<T>::value){

        res = t.size();
    }else{

        res = t.size;
    }


    return res;

}

int main() {
    std::vector<float> v(10);
    std::cout << std::boolalpha << has_size<std::vector<float>>::value <<     std::endl;
    std::cout << std::boolalpha << has_size<std::string>::value << std::endl;
    size_t res = get_size(v);
    std::cout<< res;
    return 0;
}

Функция has_size работает правильно в моем примере, но когда я пытаюсь вызвать getSize, я получаю сообщение об ошибке:

prog.cpp: In function ‘int main()’:
prog.cpp:47:24: error: the value of ‘v’ is not usable in a constant expression
  size_t res = get_size<v>;
                    ^
prog.cpp:43:21: note: ‘v’ was not declared ‘constexpr’
  std::vector<float> v(10);
                 ^
prog.cpp:47:15: error: cannot resolve overloaded function ‘get_size’ based on conversion to type ‘size_t {aka long unsigned int}’
  size_t res = get_size<v>;
           ^~~~~~~~~~~

Ответы [ 3 ]

0 голосов
/ 16 ноября 2018

Итак, обновление вашего кода немного: (для )

struct MyStruct{
    int size = 12;
};

// This function will compile only if has_size is true
template <class T,
            typename std::enable_if<has_size<T>::value, int>::type = 0>
size_t get_size(const T& t){
    return t.size();
}

// This function will compile only if has_size is FALSE (check negation !has_size)
template <class T,
            typename std::enable_if<!has_size<T>::value, int>::type = 0>
size_t get_size(const T& t){
    return t.size;
}

int main(){
    std::vector<float> v(10);
    std::cout << get_size(v) << std::endl;

    MyStruct my;
    std::cout << get_size(my) << std::endl;
    return 0;
}

Документация о std :: enable_if

Поэтому я использовал случай № 4, включенный с помощью параметра шаблона.

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


Итак, немного обновим ваш код: (из )

template <class T>
size_t get_size(const T& t){
    size_t res = 0;
    if constexpr(has_size<T>::value){
        res = t.size();
    }else{
        res = t.size;
    }
    return res;
}

int main(){
    std::vector<float> v(10);
    std::cout<< get_size(v) << std::endl;
    return 0;
}

Так меньше кода и больше читаемости:)

Это решение использует функцию из C ++ 17 , если constexpr


Почему ваше решение не работает:

if(has_size<T>::value){ // <--- this is compile time result (has_size<T>::value) so always true or always false depends on template argument which is deduced from argument type
    res = t.size(); // this need to compile always, so if it is vector then ok if something else that doesn't have such method will fail to compile
}else{
    res = t.size; // this need to compile always, again as above
}

Из небольших ошибок / улучшений:

  • мимо const&;)
  • size_t res = get_size<v>; должно быть get_size(v) будет выведен аргумент шаблона.Но да, вы можете написать также get_size<std::vector>(v)
0 голосов
/ 16 ноября 2018

Здесь много чего нужно исправить.Для начала, ваш основной size_t res = get_size<v>; не будет работать, потому что вы не можете иметь v в качестве аргумента шаблона, я предполагаю, что вместо этого он должен был get_size(v).

В get_size у вас есть это

if (has_size<T>::value) {
    res = t.size();
} else {
    res = t.size;
}

Это не сработает, потому что, хотя используется только один, компилятор видит, что вы делаете и t.size, и t.size().Я вижу, ваш вопрос помечен тегом c ++ 11, поэтому я предоставлю ответ на языке c ++ 11.

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

// using distinc values 7 and 3 to differentiate easily later
struct SizeData {
  std::size_t size = 7;
};

struct SizeFunc {
  std::size_t size() const { return 3; };
};

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

template <typename>
using void_type = void;

template <typename T, typename = void>
struct HasSizeFunc : std::false_type { };

template <typename T>
struct HasSizeFunc<T, void_type<decltype(std::declval<const T&>().size())>>
  : std::true_type { };

Я могу очень легко использовать это в основной сети, чтобы проверить, имеет ли что-то .size() или нет

int main() {
  std::cout << "SizeFunc: " << HasSizeFunc<SizeFunc>::value << '\n';
  std::cout << "SizeData: " << HasSizeFunc<SizeData>::value << '\n';
}

Но теперь для функции get_size().Как я уже говорил ранее, ваш if / else не будет работать, потому что обе ветви не компилируются (if constexpr работает, но не доступен в c ++ 11).Таким образом, вместо этого вы можете сделать то, что называется «диспетчеризацией тегов», чтобы решить, какую перегрузку функции вызывать для вызова правильного .size

// std::size_t may not be right for every type. leaving it for simplicity.
template <typename T>
std::size_t get_size_impl(T t, std::true_type) {
  return t.size();
}
template <typename T>
std::size_t get_size_impl(T t, std::false_type) {
  return t.size;
}

template <typename T>
std::size_t get_size(T t) { // note, this should probably be a const reference
  // second argument used to select an overload of get_size_impl
  return get_size_impl(t, HasSizeFunc<T>{});
}

И использовать ее:

int main() {
  SizeFunc sf;
  std::cout << "SizeFunc: " << get_size(sf) << '\n';
  SizeData sd;
  std::cout << "SizeData: " << get_size(sd) << '\n';
}

нажмите здесь, чтобы увидеть весь код в одном живом примере .Я рекомендую посмотреть эти разговоры cppcon , чтобы узнать больше.

Кроме того, здесь - это то, что я бы сделал в c ++ 17

0 голосов
/ 16 ноября 2018
if(has_size<T>::value){
    res = t.size();
}else{
    res = t.size;
}

эти ветви оцениваются во время выполнения. Поэтому обе ветви должны быть действительны во время компиляции.

#define RETURNS(...) \
  noexcept(noexcept(__VA_ARGS__)) \
  -> decltype(__VA_ARGS__) \
  { return __VA_ARGS__; }


template<class S, class...Ts>
auto select( S, Ts&&...ts )
RETURNS( std::get<S::value>(std::forward_as_tuple( std::forward<Ts>(ts)... )) )

, которая дает ветку времени компиляции.

struct call_size_t {
  template<class T>
  auto operator()( T&& t ) const
  RETURNS( t.size() )
};
struct get_size_t {
  template<class T>
  auto operator()( T&& t ) const
  RETURNS( t.size )
};

auto f = select(has_size<T>{},
  get_size_t{},
  call_size_t{}
};
res = f(t);

это довольно раздражает, потому что вы находитесь в ; в код вдвое меньше и становится тривиальным в .

...