[Trick] SFINAE4 to be continued :)
От: Quasi  
Дата: 08.12.07 17:16
Оценка: 141 (6)
Продолжение топика SFINAE4
Автор: Quasi
Дата: 06.11.07
. Предложенный там подход позволяет определять наличие членов функций по имени, без указания сигнатуры, в том числе наличие шаблонных методов и перегруженного множества методов. Отсутствие необходимости задавать сигнатуру метода является плюсом данного подхода, так не всегда возможно или удобно проверять наличие метода с определенной сигнатурой, например:
void swap(SomeClass&) или SomeClass& swap(SomeClass&),
void load(int sysNum) или void load(long sysNum) и т.д.
В общем, SFINAE4
Автор: Quasi
Дата: 06.11.07
удобен в данной ситуации, однако он не отвечает на основной вопрос, если метод есть, то будет ли вызов с определенными типами аргументов законен (well-formed). Предлагаю здесь, я думаю, наиболее мощный подход из возможных, который отвечает на этот вопрос.

Сначала, о том что он может:
struct A
{
    A(int){}; //not explicit
};

struct Possible
{
    int func(A, int* = 0){return int();}
    void func(long*) const{}
};

struct PossibleTemplate
{
    template <typename T> T* func(T t)const {return &t;}
    template <typename T> T* func(T* t){return t;}
};

struct Without{};

#define STATIC_ASSERT(condition) {int check[(condition)? 1 : -1];}

int main()
{
    // В качестве типа второго шаблонного параметра is_call_possible передается тип функции R(Arg1, Arg2, ..),
    // где ArgN - типы аргументов вызова метода, R - ожидаемый возвращаемый тип, или void, если это не интересует,
    // параметры метода считаются lvalue

    // проверяет, является ли выражение "оbj.func(i)" well-formed,
    // где оbj - экземпляр класса Possible, i - const int lvalue
    STATIC_ASSERT((is_call_possible<Possible, void (const int)>::value));
    STATIC_ASSERT((!is_call_possible<Possible, void (int*)>::value));

    // проверка вызова константного метода
    STATIC_ASSERT((is_call_possible<const Possible, void (long*)>::value));
    STATIC_ASSERT((!is_call_possible<const Possible, void (const int)>::value));

    // проверяет, является ли выражение "A a(оbj.func(i));" well-formed,
    // где оbj - экземпляр класса Possible,  i - const int lvalue
    STATIC_ASSERT((is_call_possible<Possible, A(const int)>::value));
    STATIC_ASSERT((!is_call_possible<Possible, int*(const int)>::value));

    STATIC_ASSERT((is_call_possible<PossibleTemplate, void (int)>::value));
    STATIC_ASSERT((is_call_possible<PossibleTemplate, const void* (const int*)>::value));

    STATIC_ASSERT((!is_call_possible<Without, int (int)>::value));
    return 0;
}



Казалось бы, используя has_member
Автор: Quasi
Дата: 06.11.07
, реализовать is_call_possible в случае, когда нас не интересует тип возвращаемого значения, можно как-нибудь так:
template <typename type, typename call_details>
class is_call_possible
{
    class size_class1{};
    class size_class2{size_class1 m_[2];};

    //добавляем перегруженный метод
    template <typename size_class>
    struct derived : public type
    {
        using type::func;
        size_class func(...) const;
    };

    template <bool has, typename F> 
    struct impl
    {
        static const bool value = false;
    };

    template <typename arg1> 
    struct impl<true, void(arg1)>
    {
        // в случае, если в результате перегрузки выбран метод класса type, 
        // sizeof(тип возвращаемый методом) не может быть равен одновременно
        // и sizeof(size_class1), и sizeof(size_class2)
        static const bool value =
            sizeof(((derived<size_class1>*)0)->func(*(arg1*)0))
                != sizeof(size_class1) 
                    ||
            sizeof(((derived<size_class2>*)0)->func(*(arg1*)0))
                != sizeof(size_class2);
    };

    // и т.д.
    //    template <typename arg1, typename arg2, typename r> 
    //    struct impl<true, void(arg1, arg2)> { ...

public:
    //сначала проверяем наличие метода с помощью has_member
    static const bool value = impl<has_member<type>::result, call_details>::value;
};

//Проверяем:

...........

int main()
{
    STATIC_ASSERT((is_call_possible<Possible, void (const int)>::value));
    STATIC_ASSERT((!is_call_possible<Possible, void (int*)>::value));
    STATIC_ASSERT((is_call_possible<PossibleTemplate, void (int)>::value));
    STATIC_ASSERT((!is_call_possible<Without, void (int)>::value));

    return 0;
}


Однако данный подход не работает, в случае если тип, возвращаемый методом void.
STATIC_ASSERT((is_call_possible<Possible, void (long*)>::value)); // ill-formed, потому что sizeof(void) - ill-formed


С типом void уже ничего, казалось бы, нельзя сделать, ни передать в качестве аргумента, ни преобразовать. Оказывается, решение в рамках стандарта существует, вот пример используемого трюка:

class void_exp_result{};

//Перегрузка оператора запятая 'operator,'
template <typename U> U const& operator,(U const&, void_exp_result);
template <typename U> U& operator,(U&, void_exp_result);

class double_size{ void_exp_result m_[2];};
template <typename result_type> result_type func();

#define STATIC_ASSERT(condition) {int check[(condition)? 1 : -1];}

int main()
{
    STATIC_ASSERT(sizeof(void_exp_result) != sizeof(double_size));
    STATIC_ASSERT((sizeof(func<double_size>(), void_exp_result()) == sizeof(double_size)));
    STATIC_ASSERT((sizeof(func<void>(), void_exp_result()) == sizeof(void_exp_result)));
    return 0;
}

В случае если первый аргумент оператора запятая имеет тип void, то используется встроенный оператор, иначе используется перегруженный, таким образом можно "преобразовать" тип выражения void в другой тип.

13.3.1.2.9
If the operator is the operator ,, the unary operator &, or the operator ->, and there are no viable functions,
then the operator is assumed to be the built-in operator and interpreted according to clause 5.
..............
13.3.2.2
Second, for F to be a viable function, there shall exist for each argument an implicit conversion sequence
(13.3.3.1) that converts that argument to the corresponding parameter of F. If the parameter has reference
type, the implicit conversion sequence includes the operation of binding the reference, and the fact that a
reference to non-const cannot be bound to an rvalue can affect the viability of the function (see
13.3.3.1.4).




Воспользуемся данным трюком, и, добавив проверку возвращаемого типа и проверку вызовов константных методов, получим окончательную реализацию:
namespace details
{
    template <typename type>
    class void_exp_result
    {};

    //Перегрузка оператора запятая 'operator,'
    template <typename type, typename U>
    U const& operator,(U const&, void_exp_result<type>);

    template <typename type, typename U>
    U& operator,(U&, void_exp_result<type>);

    template <typename src_type, typename dest_type>
    struct clone_constness
    {    
        typedef dest_type type;
    };

    template <typename src_type, typename dest_type>
    struct clone_constness<const src_type, dest_type>
    {
        typedef const dest_type type;
    };
}


template <typename type, typename call_details>
struct is_call_possible
{
private:
    class yes {};
    class no { yes m[2]; };

    //добавляем перегруженный метод
    struct derived : public type
    {
        using type::func;
        no func(...) const; 
    };

    //Учтем возможную константность типа класса
    typedef typename details::clone_constness<type, derived>::type derived_type;

    //Проверка результата выражения
    template <typename T, typename due_type>
    struct return_value_check
    {
        static yes deduce(due_type);
        static no deduce(...);
        //явные перегрузки в случае, если
        //due_type имеет шаблонный конструктор
        static no deduce(no);                              
        static no deduce(details::void_exp_result<type>);
    };

    //Проверка результата выражения в случае,
    //если не интересует возвращаемый тип метода
    template <typename T>
    struct return_value_check<T, void>
    {
        static yes deduce(...);
        static no deduce(no);
    };

    template <bool has, typename F> 
    struct impl
    {
        static const bool value = false;
    };

    template <typename arg1, typename r> 
    struct impl<true, r(arg1)>
    {
        static const bool value =
            sizeof(
                   return_value_check<type, r>::deduce(
                        //трюк с 'operator,'
                        (((derived_type*)0)->func(*(arg1*)0), details::void_exp_result<type>())
                                                )
                   ) == sizeof(yes);
                
    };

    // и т.д.
    //    template <typename arg1, typename arg2, typename r> 
    //    struct impl<true, r(arg1, arg2)> { ...

public:
    //сначала проверяем наличие метода с помощью has_member
    static const bool value = impl<has_member<type>::result, call_details>::value;
};

Компилировал VC7.1, VC8, Comeau C/C++ Online

Реализация имеет некоторые недостатки, которые, однако, позволяют ее использовать в подавляющем большинстве случаев:
В случае если один из перегруженных методов класса не доступен (защищенный или закрытый), или вызов с задаваемыми параметрами приводит к неоднозначности при разрешении перегрузки, то реализация является ill-formed и приведет к ошибке компиляции. Также, если класс имеет метод c сигнатурой "any_type func(...)" (метод с еllipsis), то результат возвращаемый реализацией может быть ошибочный. Надеюсь, когда-нибудь в наших проектах не будет методов с еllipsis .
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.