ложное поведение is_base_of при использовании вместе с bind - PullRequest
2 голосов
/ 19 августа 2011

Используя аргументы шаблона variadic вместе с аргументом простого шаблона, я столкнулся со странным поведением is_base_of , когда он был создан из связанного функтора.

Вот код:

template <class T, class Index>
class Base{};

template<typename T>
struct Checker {
    typedef int result_type;

    // Returns 1 if a given T type is descendant of Base<T,First>
    template<typename First, typename ...Args>
    result_type operator()(First&& first, Args&&... params)
    {
        return check(std::is_base_of<Base<T,First>, T>(),
                std::forward<First>(first),
                std::forward<Args>(params)...);
    }
    template<typename ...Args>
    result_type check(const std::true_type&, Args&&... params)
    {
        return 1;
    }
    template<typename ...Args>
    result_type check(const std::false_type&, Args&&... params)
    {
        return 0;
    }
};

struct A {};
struct B : Base<B,int> {};

int main()
{
    Checker<A> ch1;
    std::cout<<ch1(3.14)<<std::endl;
    Checker<B> ch2;
    std::cout<<ch2(1 ,3.14)<<std::endl; // output is 1
    std::cout<<std::bind(ch2, 1, 3.14)()<<std::endl; // output is 0 but it should be 1 !
    return 0;
}

Вывод программы:

0
1
0

Но я бы ожидал:

0
1
1

Неправильно ли я использую шаблоны variadic? Есть ли какой-либо другой (правильный) способ получить первый тип списка типов с переменными параметрами, например, Args? Почему это проблема, только если она используется с выражением связывания?

Обратите внимание: если я модифицирую базовый шаблон так, чтобы он содержал только один параметр шаблона, то выражение привязки работает:

template <class T>
class Base{};

template<typename T>
struct Checker {
    typedef int result_type;

    // Returns 1 if a given T type is descendant of Base<T>
    template<typename ...Args>
    result_type operator()(Args&&... params)
    {
        return check(std::is_base_of<Base<T>, T>(),
                std::forward<Args>(params)...);
    }
    template<typename ...Args>
    result_type check(const std::true_type&, Args&&... params)
    {
        return 1;
    }
    template<typename ...Args>
    result_type check(const std::false_type&, Args&&... params)
    {
        return 0;
    }
};

struct A {};
struct B : Base<B> {};

int main()
{
    Checker<A> ch1;
    std::cout<<ch1(3.14)<<std::endl;
    Checker<B> ch2;
    std::cout<<ch2(3.14)<<std::endl; // output is 1
    std::cout<<std::bind(ch2, 3.14)()<<std::endl; // output is 1 this time!
    return 0;
}

Ответы [ 2 ]

2 голосов
/ 19 августа 2011

Вы не получите ожидаемый результат, потому что тип данных First в вашем Checker функциональном объекте при вызове после std::bind() имеет тип int&, а не int.

Следовательно, std::is_base_of<Base<B,int&>, B> не создает std::true_type для вызова Checker::check.

Проблема в том, что std::bind создает объект, который внутренне хранит аргументы для функции, которую вы ему передаете. Поэтому существует именованное l-значение в качестве нестатического члена-данных объекта, возвращаемого std::bind, которое содержит значение, которое вы передали в качестве аргумента для привязки к вашей функции. Когда этот нестатический элемент данных затем передается в ссылку на r-значение во время вызова operator() функтора, он передается как ссылка на l-значение, поскольку он больше не является временным объектом. У вас была бы похожая проблема, если бы вы сделали что-то вроде:

int x = 1;
Checker<B> ch2;
std::cout<<ch2(x, 3.14)<<std::endl;

Именованное значение x является l-значением и будет передано аргументу first в вашем методе operator() как ссылка на l-значение, а не как временное, поскольку first является r -значение ссылки. Следовательно, ваш тип снова будет иметь вид int&, а не int, и вы напечатаете значение 0.

Чтобы исправить эту проблему, вы можете сделать что-то вроде:

template<typename First, typename ...Args>
result_type operator()(First&& first, Args&&... params)
{
   if (std::is_reference<First>::value)
    {
        return check(std::is_base_of<Base<T, typename std::remove_reference<First>::type>, T>(),
            std::forward<First>(first),
            std::forward<Args>(params)...);
    }
    else
    {
        return check(std::is_base_of<Base<T,First>, T>(),
            std::forward<First>(first),
            std::forward<Args>(params)...);
    }
}

Это исключит ссылочный тип объекта и даст вам нужные результаты.

0 голосов
/ 21 августа 2011

К сожалению, std :: is_reference не дал мне ожидаемого результата по более сложной проблеме.Итак, в конце концов я решил предоставить перегруженные ссылки и константные ссылки:

...