проверьте, существует ли член, используя enable_if

Вот что я пытаюсь сделать:

template <typename T> struct Model
{
    vector<T> vertices ;

    #if T has a .normal member
    void transform( Matrix m )
    {
        each vertex in vertices
        {
          vertex.pos = m * vertex.pos ;
          vertex.normal = m * vertex.normal ;
        }
    }
    #endif

    #if T has NO .normal member
    void transform( Matrix m )
    {
        each vertex in vertices
        {
          vertex.pos = m * vertex.pos ;
        }
    }
    #endif
} ;

Я видел примеры использования enable_if, но я не могу понять, как применить enable_if к этой проблеме и можно ли его вообще применить.


person bobobobo    schedule 09.12.2012    source источник
comment
enable_if не используется для проверки существования члена, а используется для удаления перегрузок.   -  person Pubby    schedule 09.12.2012
comment
Разве я не могу использовать его, чтобы сделать что-то вроде (предложение в редактировании выше)?   -  person bobobobo    schedule 09.12.2012
comment
Нет, вам нужен static if, которого еще не существует. То, что вы хотите, вполне возможно, просто он не будет использовать такой синтаксис.   -  person Pubby    schedule 09.12.2012


Ответы (6)


С C++11 это стало намного проще.

template <typename T> struct Model
{
    vector<T> vertices;

    void transform( Matrix m )
    {
        for(auto &&vertex : vertices)
        {
          vertex.pos = m * vertex.pos;
          modifyNormal(vertex, m, special_());
        }
    }

private:

    struct general_ {};
    struct special_ : general_ {};
    template<typename> struct int_ { typedef int type; };

    template<typename Lhs, typename Rhs,
             typename int_<decltype(Lhs::normal)>::type = 0>
    void modifyNormal(Lhs &&lhs, Rhs &&rhs, special_) {
       lhs.normal = rhs * lhs.normal;
    }

    template<typename Lhs, typename Rhs>
    void modifyNormal(Lhs &&lhs, Rhs &&rhs, general_) {
       // do nothing
    }
};

Что следует отметить:

  • Вы можете именовать нестатические элементы данных в decltype и sizeof без необходимости в объекте.
  • Вы можете применить расширенный SFINAE. В принципе любое выражение можно проверить, и если оно недействительно при подстановке аргументов, шаблон игнорируется.
person Johannes Schaub - litb    schedule 09.12.2012
comment
Это определенно намного лучше SFINAE, чем ранее, где вам приходилось создавать детектор элементов для каждого тип члена, который вы хотите обнаружить. Но почему люди всегда переходят к SFINAE, я предпочитаю использовать введите черты, чтобы сделать этот тип условной компиляции сейчас. - person bobobobo; 09.12.2012
comment
Я думаю, это зависит от того, есть ли у вас больше типов для проверки или больше членов для проверки? - person Dalibor Frivaldsky; 01.08.2014
comment
Черты типа помогают только в том случае, если вы знаете типы заранее. В вашей ссылке вы должны знать, что VertexN - это специальный класс. Таким образом, они могут работать в примере OP, но SFINAE работает, даже если вы не знаете, какими будут другие типы, что IMO является гораздо лучшей инкапсуляцией. - person Kevin Holt; 09.10.2015
comment
Мне понравилось использование general_ и special_, мне это показалось более понятным, чем использование int и long. - person tlonuk; 15.09.2016

Вам нужна мета-функция для обнаружения вашего члена, чтобы вы могли использовать enable_if. Идиома для этого называется Member Detector. Это немного сложно, но это можно сделать!

person ltjax    schedule 09.12.2012

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

В следующем примере проверяется, содержит ли переданный тип c_str() функциональный член:

template <typename, typename = void>
struct has_c_str : false_type {};

template <typename T>
struct has_c_str<T, void_t<decltype(&T::c_str)>> : std::is_same<char const*, decltype(declval<T>().c_str())>
{};

template <typename StringType,
          typename std::enable_if<has_c_str<StringType>::value, StringType>::type* = nullptr>
bool setByString(StringType const& value) {
    // use value.c_str()
}

В случае, если необходимо выполнить проверки, содержит ли переданный тип определенный элемент данных, можно использовать следующее:

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

template <typename T>
struct has_field<T, std::void_t<decltype(T::field)>> : std::is_convertible<decltype(T::field), long>
{};

template <typename T,
          typename std::enable_if<has_field<T>::value, T>::type* = nullptr>
void fun(T const& value) {
    // use value.field ...
}

ОБНОВЛЕНИЕ C++20

C++20 представил ограничения и концепции, основные функции языка в этой версии C++.

Если мы хотим проверить, содержит ли параметр шаблона c_str функцию-член, то следующее будет работать:

template<typename T>
concept HasCStr = requires(T t) { t.c_str(); };

template <HasCStr StringType> 
void setByString(StringType const& value) {
    // use value.c_str()
}

Кроме того, если мы хотим проверить, существует ли элемент данных, который можно преобразовать в long, можно использовать следующее:

template<typename T>
concept HasField = requires(T t) {
    { t.field } -> std::convertible_to<long>;
};

template <HasField T> 
void fun(T const& value) {
    // use value.field
}

Используя C++20, мы получаем гораздо более короткий и читаемый код, который ясно выражает его функциональность.

person NutCracker    schedule 10.11.2019
comment
Версия C++20, безусловно, является лучшим и наиболее удобочитаемым решением. Я (ab) использовал это, чтобы эффективно добавить реализацию по умолчанию функции-члена post-hoc в существующую библиотеку типов, не изменяя их, чтобы наследовать реализацию по умолчанию от базового класса: template <class T> auto call_member(T t) { if constexpr(HasMember<T>) { return t.member(); } else { return default_action(); } }. (Концептуальное требование — это просто constexpr логическое выражение, поэтому его можно использовать в if constexpr таким образом) - person Irfy; 21.05.2020

Это не ответ на ваш конкретный случай, но это альтернативный ответ на заголовок вопроса и проблему в целом.

#include <iostream>
#include <vector>

struct Foo {
    size_t length() { return 5; }
};

struct Bar {
    void length();
};

template <typename R, bool result = std::is_same<decltype(((R*)nullptr)->length()), size_t>::value>
constexpr bool hasLengthHelper(int) { 
    return result;
}

template <typename R>
constexpr bool hasLengthHelper(...) { return false; }

template <typename R>
constexpr bool hasLength() {
    return hasLengthHelper<R>(0);
}

// function is only valid if `.length()` is present, with return type `size_t`
template <typename R>
typename std::enable_if<hasLength<R>(), size_t>::type lengthOf (R r) {
  return r.length();
}

int main() {
    std::cout << 
      hasLength<Foo>() << "; " <<
      hasLength<std::vector<int>>() << "; " <<
      hasLength<Bar>() << ";" <<
      lengthOf(Foo()) <<
      std::endl;
    // 1; 0; 0; 5

    return 0;
}

Соответствующие https://ideone.com/utZqjk.

Спасибо dyreshark на свободном узле IRC #c++.

person deceleratedcaviar    schedule 15.03.2017

template<
typename HTYPE, 
typename = std::enable_if_t<std::is_same<decltype(HTYPE::var1), decltype(HTYPE::var1)>::value>
>
static void close_release
(HTYPE* ptr) {
    ptr->var1;
}

Использование enable_if и decltype, чтобы позволить компилятору проверять переменную, надеюсь, поможет.

person vrqq    schedule 21.03.2020
comment
Я люблю это, спасибо! - person Alex Henrie; 30.01.2021
comment
std::is_same<decltype(HTYPE::var1), decltype(HTYPE::var1)>::value можно было бы проще записать как std::is_pointer<decltype(&HTYPE::var1)>::value. Кроме того, добавление & позволяет избежать ошибки компилятора при проверке наличия функции. - person Alex Henrie; 30.01.2021

Я знаю, что немного поздно, однако...

typedef int Matrix;

struct NormalVertex {
    int pos;
    int normal;
};

struct Vertex {
    int pos;
};

template <typename T> struct Model
{
    typedef int No;
    typedef char Yes;

    template<typename U> static decltype (declval<U>().normal, Yes()) has_normal(U a);
    static No has_normal(...);

    vector<T> vertices ;

    template <typename U = T>
    typename enable_if<sizeof(has_normal(declval<U>())) == sizeof(Yes), void>::type
    transform( Matrix m )
    {
        std::cout << "has .normal" << std::endl;
        for (auto vertex : vertices)
        {
          vertex.pos = m * vertex.pos ;
          vertex.normal = m * vertex.normal ;
        }
    }

    template <typename U = T>
    typename enable_if<sizeof(has_normal(declval<U>())) == sizeof(No), void>::type
    transform( Matrix m )
    {
        std::cout << "has no .normal" << std::endl;
        for (auto vertex : vertices)
        {
          vertex.pos = m * vertex.pos ;
        }
    }
} ;

int main()
{
    Matrix matrix;
    Model <NormalVertex> normal_model;

    Vertex simple_vertex;
    Model <Vertex> simple_model;

    simple_model.transform(matrix);
    normal_model.transform(matrix);

    return 0;
}
person misterx527    schedule 05.11.2019