Топ контрибуторов
loading
loading
Знаете ли Вы, что

После прохождения теста можно комментировать вопросы теста, а Ваши комментарии увидят модераторы теста и пользователи, которым когда-либо эти вопросы попадались.

Лента обновлений
ссылка Oct 22 23:24
Комментарий от alexcei88:
В вопросе переменная s даже не выводиться, а выводитьс...
ссылка Oct 22 17:46
Комментарий от AlexFurm:
Это UB, так можно вызывать только статические функции ч...
ссылка Oct 22 17:43
Комментарий от AlexFurm:
Любые битовые операции с signed это UB
ссылка Oct 21 20:30
Комментарий от yoori:
Любой вариант скомпилируется если компилировать не в конеч...
ссылка Oct 21 16:53
Добавлен вопрос в тест QA (Quality Assurance)
Статистика

Тестов: 153, вопросов: 8596. Пройдено: 443367 / 2177508.

Перегрузка функций по возвращаемому значению (c++ function overloading)

head tail Статья
категория
C++
дата15.05.2009
авторanalizer
голосов12

Мы рождены, чтоб сказку сделать былью (c) "Авиамарш"

Рекомендуемая литература:

Стандарт

13.1 Overloadable declarations

2    Certain function declarations cannot be overloaded:
      — Function declarations that differ only in the return type cannot be overloaded.

Выглядит достаточно чётко и безапелляционно, не правда ли? Но согласитесь, всё же очень хотелось бы иметь возможность иметь единую запись вызова функции для разных типов возвращаемых данных, например для генерации случайных значений заданной длины, допустим, строк и целых чисел:

template <typename T>
void f()
{
	T t = my_rand(10);
	...
}
// my_rand для строк: количество символов - n
{
	std::string result;
	while(n-- > 0)
	{
		result += char('A' + rand() % 26);
	}
	return result;
}
// my_rand для целых чисел: количество бит - n
{
	unsigned result = 0;
	while(n-- > 0)
	{
		result <<= 1;
		result += rand() & 1;
	}
	return result;
}
...
f<std::string>();	// получим, например, QWERTYUIOP
f<unsigned>()		// а здесь - 1018
Как решить данную проблему? Например, можно ввести дополнительный фиктивный параметр для функции, по которому и будет происходить перегрузка, но это - не труЪ, ибо лишний параметр - это грязь. Есть решение куда более изящное и красивое. Достаточно лишь хитро определить my_rand:
std::string string_impl(unsigned n)
{
	std::string result;
	while(n-- > 0)
	{
		result += char('A' + rand()%26);
	}
	return result;
}

unsigned unsigned_impl(unsigned n)
{
	unsigned result = 0;
	while(n-- > 0)
	{
		result <<= 1;
		result += rand() & 1;
	}
	return result;
}

struct my_rand
{
	const unsigned n_;
	my_rand(unsigned n)
		: n_(n)
	{}
	inline operator std::string() const
	{
		return string_impl(n_);
	}
	inline operator unsigned() const
	{
		return unsigned_impl(n_);
	}
};
...
f<unsigned>();
f<std::string>();
(unsigned)my_rand(5);
(std::string)my_rand(6);

Работает! Что здесь происходит? Ничего сверхъестественного - в строке T t = my_rand(10); создаётся временный объект типа my_rand, который запоминает переданный в конструктор параметр, и тут же приводится к типу переменной которой присваивается значение, при помощи явно определённых операторов приведения типа. Всё просто и понятно, но тем не менее мы смогли эмулировать перегрузку поведения функции по возвращаемому значению.

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

Хочется странного

А именно:

  1. Чтобы параметры, их тип и количество можно быть разными для каждого возвращаемого типа. Например чтобы для целых длина была равна десяти битам, для строк - пять символов, а для пар строк - три и четыре символа.
  2. Автоматически реализовывать класс производящий перегрузку.
  3. Перегружать по любому количеству типов, а не только двум, без особых осложнений.
  4. Иметь возможность использовать функции выполняющие операции для какого либо типа в нескольких перегрузках.
  5. Проверять наличие дублирование возвращаемого типа с списке перегрузки и выдавать соответствующую meaningful ошибку.

В решении этих задач нам помогут (по пунктам):

  1. Boost.Bind, который позволит связывать указатели на функции с параметрами, которые необходимо в них передать.
  2. Boost.MPL, который позволит пробегать по списку типов, и для каждого типа в списке - генерировать класс содержащий соответствующий оператор приведения типа и хранящий указатель на функцию (или же binder).
  3. Boost.Preprocessor, который позволит сгенерировать любое количество функций, принимающих в себя любое количество параметров.
  4. Собственно это уже есть в примере выше ☺. Но правило есть правило - вот ещё одна ссылка (всё равно тоже воспользуемся): Boost.TypeTraits
  5. Статические проверки о которых я уже писал. Ну и Boost.MPL, разумеется.

Для начала создадим метафункцию, которая будет определять тип возвращаемого значения переданного указателя на функцию или binder'а (обращаю ваше внимание, что многие строки кода содержат объяснения, достаточно навести на них указателем мыши):

#include <boost/mpl/and.hpp>
#include <boost/mpl/bool.hpp>
#include <boost/type_traits/function_traits.hpp>
#include <boost/type_traits/remove_pointer.hpp>
#include <boost/type_traits/is_function.hpp>
#include <boost/type_traits/is_pointer.hpp>

template<class is_function,class T>
struct get_result_type_impl
{
    typedef typename boost::function_traits<typename
		boost::remove_pointer<T>::type>::result_type result_type;
};

template<class T>
struct get_result_type_impl<boost::mpl::false_,T>
{
    typedef typename T::result_type result_type;
};

template<class T>
struct get_result_type
{
    typedef typename get_result_type_impl<
        typename boost::mpl::and_<
            boost::is_pointer<T>,
            boost::is_function<typename boost::remove_pointer<T>::type>
        >::type,
        T
    >::result_type result_type;
};

Двигаемся дальше - теперь нам нужно реализовать цепочку классов, каждый из которых будет:

  1. В качестве параметров шаблона принимать тип, по которому он должен обеспечить перегрузку, и список типов (boost::mpl::list), которые ещё осталось перегрузить.
  2. Перегружать переданный тип и запоминать указатель на соответсвующую функцию или хранить binder.
  3. Наследоваться от такого же шаблонного класса, передавая ему в качестве первого параметра - первый элемент из списка типов, а в качестве второго - список типов, без первого.

Выглядит это примерно вот так:

#include <static_check.hpp>
#include <boost/mpl/list.hpp>
#include <boost/mpl/set.hpp>
#include <boost/mpl/copy.hpp>
#include <boost/mpl/size.hpp>
#include <boost/mpl/pop_front.hpp>
#include <boost/mpl/front.hpp>
#include <boost/mpl/insert.hpp>
#include <boost/mpl/inserter.hpp>
template<class type,class l>
struct proxy_impl : proxy_impl<
    typename boost::mpl::front<l>::type,
    typename boost::mpl::pop_front<l>::type>
{
    typedef proxy_impl<
		typename boost::mpl::front<l>::type,
		typename boost::mpl::pop_front<l>::type
	> base;
    proxy_impl(type t,const base& b)
        :t_(t),base(b)
    {}
    operator typename get_result_type<type>::result_type()const
    {
        return t_();
    }
private:
    const type t_;
};

template<class type>
struct proxy_impl<type,boost::mpl::l_end>
{
    proxy_impl(type t)
        :t_(t)
    {}
    operator typename get_result_type<type>::result_type()const
    {
        return t_();
    }
private:
    const type t_;
};

template<class l>
struct proxy : proxy_impl<
        typename boost::mpl::front<l>::type,
        typename boost::mpl::pop_front<l>::type>
{
	STATIC_CHECK((
		boost::mpl::size<
			typename boost::mpl::copy<
				l,
				boost::mpl::inserter<boost::mpl::set<>, boost::mpl::insert<boost::mpl::_1,boost::mpl::_2> >
			>::type
		>::value == boost::mpl::size<l>::value
		), each_return_type_in_the_functions_list_must_be_unique);

    typedef proxy_impl<
        typename boost::mpl::front<l>::type,
        typename boost::mpl::pop_front<l>::type
    > base;
};

Чудно, не правда ли? Дело за малым - нужно сгенерировать кучу функций, которые будут создавать все нужные нам прокси-классы, на основе переданных параметров:

#include <boost/preprocessor/repetition.hpp>
#ifndef RETTYPE_OVERLOAD_MAX_COUNT
#define RETTYPE_OVERLOAD_MAX_COUNT 10
#endif

template<class T0>
typename proxy<boost::mpl::list<T0> >::base overloader(T0 t0)
{
    return typename proxy<boost::mpl::list<T0> >::base(t0);
}

#define RETTYPE_generate(z,n,unused)                                    \
    template<BOOST_PP_ENUM_PARAMS(n,class T)>                           \
    typename proxy<                                                     \
            boost::mpl::list<BOOST_PP_ENUM_PARAMS(n,T)>                 \
    >::base                                                             \
    overloader(BOOST_PP_ENUM_BINARY_PARAMS(n,T,t))                      \
    {                                                                   \
            return typename proxy<                                      \
                    boost::mpl::list<BOOST_PP_ENUM_PARAMS(n,T)>         \
            >::base(t0,overloader(BOOST_PP_ENUM_SHIFTED_PARAMS(n,t)));  \
    }

BOOST_PP_REPEAT_FROM_TO(2,RETTYPE_OVERLOAD_MAX_COUNT,RETTYPE_generate,~)

#undef RETTYPE_generate

Теперь вы можете перегружать что угодно и передавать какие угодно параметры, например вы может делать так:

#include <string>
#include <utility>
#include <boost/bind.hpp>

enum color {RED, GREEN, BLUE, COLOR_END};

color color_impl()
{
	return color(rand() % COLOR_END);
}

std::string string_impl(unsigned n)
{
	std::string result;
	while(n-- > 0)
	{
		result += char('A' + rand() % 26);
	}
	return result;
}

unsigned unsigned_impl(unsigned n)
{
	unsigned result = 0;
	while(n-- > 0)
	{
		result <<= 1;
		result += rand() & 1;
	}
	return result;
}

std::pair<std::string, std::string> double_string_impl(unsigned n1,unsigned n2)
{
	return std::make_pair(string_impl(n1), string_impl(n2));
}

template <typename T>
void f()
{
	T t = overloader(
		color_impl,
		boost::bind(string_impl, 5),
		boost::bind(unsigned_impl, 10),
		boost::bind(double_string_impl, 3, 4)
	);
	...
}
...
f<color>();
f<std::string>();
f<unsigned>();
f< std::pair<std::string, std::string> >();
//color c = overloader(color_impl, color_impl); // вызовет ошибку компиляции, так как возвращаемые типы совпадут.

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

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

----

Дмитрий analizer Потапов, апрель 2009

Если Вам понравилась статья, проголосуйте за нее

Голосов: 12  loading...
c0nst   sgauStudent   alexis112   serj   Engineer9   turanga_leela   Lavroff   sonjawnuk   nickolayl   onehatedfate   Riddler   ITcrusader