Как запретить неявное преобразование аргумента при вызове функции?
(Спрашиваю вторично, искуственный пример, которым был снабжен вопрос при первом его появлении был очевидно неудачен.)
Ниже подробно описаны обстоятельства появления вопроса.
-----------------------------------------------------------------------------------------------------------
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 в случае, если предикат критерия сортировки не умеет сравнивать объекты добавляемого типа с объектами типа элементов контейнера.
Этого можно было бы добиться, запретив неявное преобразование при вызове предиката критерия сортировки. Но как?
Здравствуйте, igna, Вы писали:
Проблему понял.
Имею вопрос, ка грится, лудовлетворения любопытсва для:
Что мешает хранить указатели?
... << RSDN@Home 1.1.4 stable rev. 510>>
Здравствуйте, 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>>
Здравствуйте, 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;
}
Примерное направление задал
Здравствуйте, 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;
}
Здравствуйте, srggal, Вы писали:
S>Что мешает хранить указатели?
Тоже можно. Но тогда не надо будет менять библиотеку.
Здравствуйте, igna, Вы писали:
I>Интересная техника.
Дык, самому нравиться, но никто не отвечает....
А сам что-то не расковырял до конца ;(
... << RSDN@Home 1.1.4 stable rev. 510>>
Здравствуйте, igna, Вы писали:
I>Здравствуйте, VoidEx:
I>Спасибо, это то, что я спрашивал.
I>Правда теперь хочется большего. А именно, чтобы при использовании запрета на неявное преобразование внутри шаблона вместо ошибки компиляции шаблон не инстанцировался бы, точнее инстанцировался бы для других типов, не требующих неявного преобразования внутри шаблона. А неявное преобразование выполнялось бы перед вызовом шаблонной функции.
Так уже вряд ли получится, к сожалению... Хотя кто его знает %)