Запрет неявного преобразования при вызове функции (2)
От: igna Россия  
Дата: 21.10.05 09:25
Оценка:
Как запретить неявное преобразование аргумента при вызове функции?

(Спрашиваю вторично, искуственный пример, которым был снабжен вопрос при первом его появлении был очевидно неудачен.)

Ниже подробно описаны обстоятельства появления вопроса.

-----------------------------------------------------------------------------------------------------------

1. Как в стандартный контейнер "добавить" (*) объект типа неявно преобразуемого к типу элементов контейнера
2. Зачем это кому-то нужно
3. Проблема
4. Решение проблемы. Попытка 1
5. Решение проблемы. Попытка 2
6. Решение проблемы. Попытка 3
7. Хорошо бы

(*) Слово "добавить" взято в кавычки, потому-что добавляется конечно не сам объект, а результат преобразования.


1. Как в стандартный контейнер "добавить" объект типа неявно преобразуемого к типу элементов контейнера

Просто. При вызове push_back(const value_type&) или insert(const value_type&) создается временный объект типа элементов контейнера, который и копируется в контейнер.


2. Зачем это кому-то нужно

Например чтобы скопировать контейнер элементов типа A в контейнер элементов типа B, причем преобразование типа A в тип B определено. Или библиотечная функция возвращает объекты типа A, информация содержащаяся в которых нужна лишь частично. Определяем тип B включая конструктор B(const A&) и храним в контейнере объекты типа B.


3. Проблема

К сожалению создаются временные объекты, если только тип A не является производным от типа B. Произвести A от B не всегда возможно, например A может быть частью библиотеки. Неявного создания временных объектов можно избежать создав один временный объект типа элементов контейнера явно и производя все добавления посредством его. Тем не менее остается избыточное копирование, а в некоторых случаях при копировании возможно обращение к new и delete (если объект содержит указатели на свободную память). Кроме того явное создание одного временного объекта не поможет в случае использования стандартных алгоритмов, например transform.

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


4. Решение проблемы. Попытка 1

Сделаем функцию добавления шаблонной, например:
    template <typename U> void push_back(const U&)

или
    template <typename U> pair<iterator, bool> insert(const U&)

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

В случае контейнера vector это пожалуй и все, но в случае контейнера set такое переопределение избегая создания одного временного объекта перед вызовом insert приводит к созданию многих временных объектов перед вызовом предиката критерия сортировки!


5. Решение проблемы. Попытка 2

Переопределим предикат less (а также другие стандартные предикаты) следующим образом:
    struct less {
        template <typename T1, typename T2>
            bool operator()(const T1& x, const T2& y)
                { return x < y; }
    };

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

И все же если такой оператор< не определен или пользователь передаст в качестве критерия сортировки предикат умеющий сравнивать только объекты типа элементов контейнера, создания временных объектов избежать не удается.


6. Решение проблемы. Попытка 3

Назовем шаблонную функцию добавления не insert, а insert_comparable, привлекая внимание к тому, что в качестве аргумента следует использовать только объекты типа сравнимого с типом элементов контейнера. Кроме того этим обеспечивается совместимость с сегодняшними стандартными контейнерами, так как работа insert не изменяется, преобразование выполняется один раз перед вызовом insert, в то время как при вызове insert_comparable преобразование либо не выполняется вовсе (если предикат критерия сортировки умеет сравнивать элементы различного типа), либо выполняется много раз.

Функция insert при этом определяется просто как
    pair<iterator, bool> insert(const value_type& x) { return insert_comparable(x); }


7. Хорошо бы

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

Этого можно было бы добиться, запретив неявное преобразование при вызове предиката критерия сортировки. Но как?
Re: Запрет неявного преобразования при вызове функции (2)
От: srggal Украина  
Дата: 21.10.05 10:58
Оценка:
Здравствуйте, igna, Вы писали:

Проблему понял.

Имею вопрос, ка грится, лудовлетворения любопытсва для:
Что мешает хранить указатели?
... << RSDN@Home 1.1.4 stable rev. 510>>
Re: Запрет неявного преобразования при вызове функции (2)
От: srggal Украина  
Дата: 21.10.05 14:07
Оценка: 1 (1)
Здравствуйте, igna, Вы писали:

Вот что у меня получилось:

#include "stdafx.h"
#include <string>
#include <iostream>
#include <sstream>
#include <functional>

#include <boost\static_assert.hpp>

namespace
{
    template < typename T,  T val >
    struct member_wrapper{};

    template < typename T1, typename T2 >
        char test_for_compare(
                    const T1&,
                    const T2&,
                    member_wrapper
                        <    
                            bool (T1::*)(const T2&),
                            &T1::operator<  
                        >>* p = 0
                );

    template < typename T1, typename T2 >
        double test_for_compare(const T1&,const T2&, ...);

    template < typename T1, typename T2 >
        struct has_member_compare
        {
            static T1 t1;
            static T2 t2;
            static const bool value = (1 == sizeof(test_for_compare(t1, t2)));
        };

    template < typename T1, typename T2 >
        char test_for_user_conversion(
                    const T1&,
                    const T2&,
                    member_wrapper
                        <    
                            T2 (T1::*)(),        // какова сигнатура оператора пользовательского приведения типа ?
                            &T1::operator T2
                        >>* p = 0
                );

    template < typename T1, typename T2 >
        double test_for_user_conversion(const T1&,const T2&, ...);

    template < typename T1, typename T2 >
        struct has_user_conversion
        {
            static T1 t1;
            static T2 t2;
            static const bool value = (1 == sizeof(test_for_user_conversion(t1, t2)));
        };

    //template < typename T1, typename T2 >
    //    char test_for_unary_pred(
    //                const T1&,
    //                const T2&,
    //                member_wrapper
    //                    <    
    //                        bool (T1::*)(const T2&),
    //                        &T1::operator()  // warning C4346: 'T1::()' : dependent name is not a type

    //                    >* p = 0
    //            );



    template < typename T1, typename T2 >
        double test_for_unary_pred(const T1&,const T2&, ...);


    template < typename T1, typename T2 >
        struct is_unary_pred
        {
            static T1 t1;
            static T2 t2;
            static const bool value = (1 == sizeof(test_for_unary_pred(t1, t2)));
        };
};

struct Foo
{
};

struct Bar
{
    bool operator< ( const Foo& );
};

struct Baz
{
    operator Bar();
};

template< typename ValueT, typename Pred  = std::less<ValueT>  >
struct Container
{

    template< typename T >
        void insert_comparable( const T& val )
        {
            BOOST_STATIC_ASSERT( 
                    (!has_member_compare< ValueT, T >::value 
                            && 
                    !has_user_conversion< Baz, Bar >::value) 
                );
            // BOOST_STATIC_ASSERT( (!is_unary_pred< ValueT, T >::value) );            
        }
};

int main()
{
    Container< Bar >        cont;


//    cont.insert_comparable( Foo()  );    // Не компилируется
    cont.insert_comparable( Baz()  );    // Не хотелось бы чтобы это компилировалось
    cont.insert_comparable( Bar()  );

    return 0;
}
S>


Т.е. вместо сабжа, просто проверяем чтобы функтор был правильный

Но, в данной версии я смог реализовать только проверку на operator<.

Вопрос:
1) Какова сигнатура пользовательского приведения типа, точнее, почему has_user_conversion работает неправильно ? ( см. второй выделенный комментаий в коде )
2) Почему в шаблоне нельзя параметризоать шаблон T::operator() ? ( см. второй выделенный комментаий в коде ), Может Ди-Три-Графы ?


ЗЫ код исключительно примерный, чтобы просто показать мысль

ЗЫЗЫ Код основан на реальных исторических событиях, а именно, на первом посте всеми заслуженно уважаемого, но иногда черезчур категоричного — MaximE.
... << RSDN@Home 1.1.4 stable rev. 510>>
Re: Запрет неявного преобразования при вызове функции (2)
От: VoidEx  
Дата: 23.10.05 12:03
Оценка: 5 (2)
Здравствуйте, igna, Вы писали:

I>Как запретить неявное преобразование аргумента при вызове функции?



//------------------------------------------------------------------------------
template <bool Condition>
class DisableCall;
//------------------------------------------------------------------------------
template <>
class DisableCall<false> {};
//------------------------------------------------------------------------------
template <typename Type>
class Convertable
{
public:
    Convertable(Type Origin) : value(Origin) {}
    template <typename OtherType>
    Convertable(OtherType Origin)
    {
        DisableCall<true>();
    }
    operator Type() { return value; }
protected:
private:
    Type value;
};
//------------------------------------------------------------------------------
template <typename FunctionType>
class ExplicitCaller;
//------------------------------------------------------------------------------
template <typename Return, typename Type0, typename Type1>
class ExplicitCaller<Return (Type0, Type1)>
{
    typedef Return FunctionType(Type0, Type1);
public:
    ExplicitCaller(FunctionType &Function) : function(Function) {}
    Return operator () (Convertable<Type0> Parameter0, Convertable<Type1> Parameter1)
    {
        return function(Parameter0, Parameter1);
    }
protected:
private:
    FunctionType &function;
};
//------------------------------------------------------------------------------
template <typename FunctionType>
ExplicitCaller<FunctionType> Explicit(FunctionType &Function)
{
    return ExplicitCaller<FunctionType>(Function);
}
//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
void foo(int a, float b)
{
}
//------------------------------------------------------------------------------
int main()
{
    foo(1, 1);
    //Explicit(foo)(1, 1);
    Explicit(foo)(1, 1.f);
    return 0;
}


Примерное направление задал
Re[2]: Запрет неявного преобразования при вызове функции (2)
От: igna Россия  
Дата: 24.10.05 13:55
Оценка:
Здравствуйте, VoidEx:

Спасибо, это то, что я спрашивал.

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


...
void foo(int a, float b)
{
}
//------------------------------------------------------------------------------
template <typename T1, typename T2> void bar(T1 a, T2 b)
{
    Explicit(foo)(a, b);
}
//------------------------------------------------------------------------------
int main()
{
    foo(1, 1);
    //Explicit(foo)(1, 1);
    Explicit(foo)(1, 1.f);

    //bar(1, 1); // А вот здесь неявное преобразование хотелось бы разрешить
    bar<int, float>(1, 1);
    bar(1, 1.f);

    return 0;
}
Re[2]: Запрет неявного преобразования при вызове функции (2)
От: igna Россия  
Дата: 24.10.05 14:13
Оценка:
Здравствуйте, srggal:

Интересная техника.
Re[2]: Запрет неявного преобразования при вызове функции (2)
От: igna Россия  
Дата: 24.10.05 14:21
Оценка:
Здравствуйте, srggal, Вы писали:

S>Что мешает хранить указатели?


Тоже можно. Но тогда не надо будет менять библиотеку.
Re[3]: Запрет неявного преобразования при вызове функции (2)
От: srggal Украина  
Дата: 24.10.05 14:44
Оценка:
Здравствуйте, igna, Вы писали:

I>Интересная техника.


Дык, самому нравиться, но никто не отвечает....

А сам что-то не расковырял до конца ;(
... << RSDN@Home 1.1.4 stable rev. 510>>
Re[3]: Запрет неявного преобразования при вызове функции (2)
От: VoidEx  
Дата: 25.10.05 12:16
Оценка:
Здравствуйте, igna, Вы писали:

I>Здравствуйте, VoidEx:


I>Спасибо, это то, что я спрашивал.


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


Так уже вряд ли получится, к сожалению... Хотя кто его знает %)
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.