Переопределение параметров вариации

Я столкнулся с необходимостью переупорядочивать вариационный список параметров, который предоставляется конструктору структуры. После переупорядочения на основе их типов параметры будут сохранены в виде кортежа. Мой вопрос заключается в том, как это можно сделать, чтобы современный компилятор С++ (например, g++-4.7) не генерировал ненужные инструкции по загрузке или хранению. То есть, когда конструктор вызывается со списком параметров переменного размера, он эффективно подталкивает каждый параметр на место на основе упорядочения по типам параметров.

Вот конкретный пример. Предположим, что базовый тип каждого параметра (без ссылок, ссылок rvalue, указателей или квалификаторов) - это char, int или float. Как я могу сделать это так, чтобы сначала отображались все параметры базового типа char, а затем все из базового типа int (что оставляет параметры базового типа float последним). Относительный порядок, в котором были заданы параметры, не должен нарушаться внутри подмножеств однородного базового типа.

Пример: foo::foo() вызывается с аргументами float a, char&& b, const float& c, int&& d, char e. Кортеж tupe std::tuple<char, char, int, float, float>, и он построен так: tuple_type{std::move(b), e, std::move(d), a, c}.

Рассмотрим структуру, определенную ниже, и предположим, что метафунт deduce_reordered_tuple_type уже реализован. Как бы вы написали конструктор так, чтобы он работал по назначению? Если вы считаете, что код для deduce_reodered_tuple_type, был бы вам полезен, я могу его предоставить; он немного длинный.

template <class... Args> struct foo
{
    // Assume that the metafunction deduce_reordered_tuple_type is defined.
    typedef typename deduce_reordered_tuple_type<Args...>::type tuple_type;
    tuple_type t_;

    foo(Args&&... args) : t_{reorder_and_forward_parameters<Args>(args)...} {}
};

Изменить 1 Техника, описанная выше, имеет приложения в математических рамках, которые сильно используют шаблоны выражений, вариационные шаблоны и метапрограммирование для выполнения агрессивной вставки. Предположим, что вы хотите определить оператор, который принимает произведение нескольких выражений, каждое из которых может быть передано по ссылке, ссылка на константу или ссылку rvalue. (В моем случае выражения являются условными таблицами вероятности, а операция является факторным продуктом, но что-то вроде матричного умножения работает соответственно.)

Для оценки продукта вам необходим доступ к данным, предоставленным каждым выражением. Следовательно, вы должны перемещать выражения, переданные как ссылки rvalue, копировать выражения, переданные по ссылке на const, и принимать адреса выражений, переданных по ссылке. Используя описанную выше технику, вы получаете несколько преимуществ.

  • Другие выражения могут использовать единообразный синтаксис для доступа к элементам данных из этого выражения, поскольку все тяжелые работы метапрограммирования выполняются заранее, внутри класса.
  • Мы можем сохранять пространство стека, группируя указатели вместе и сохраняя более крупные выражения в конце кортежа.
  • Реализация некоторых типов запросов становится намного проще (например, проверьте, не указал ли какой-либо из указателей, сохраненных в алиасах кортежа, заданный указатель).

Большое спасибо за вашу помощь!

Ответ 1

Счастливое 4 июля всех! Хорошо, здесь вы идете.

Оказывается, что g++-4.7 довольно классно при обработке и создает идентичный машинный код в соответствии с моим тестированием (инструкции для воспроизведения результатов ниже кода).

#include <iostream>
#include <tuple>
#include <typeinfo>
#include <type_traits>

template <class... Args>
struct sequence
{
    typedef std::tuple<Args...> tuple_type;
};

template <class U, class V>
struct sequence_cat;

template <class... U, class... V>
struct sequence_cat<sequence<U...>, sequence<V...>>
{
    typedef sequence<U..., V...> type;
};

template <class... V>
struct sequence_cat<void, sequence<V...>>
{
    typedef sequence<V...> type;
};

template <class... U>
struct sequence_cat<sequence<U...>, void>
{
    typedef sequence<U...> type;
};

template <>
struct sequence_cat<void, void>
{
    typedef void type;
};

template <class T>
struct undecorate
{
    typedef typename std::remove_reference<T>::type noref_type;
    typedef typename std::remove_pointer<noref_type>::type noptr_type;
    typedef typename std::remove_cv<noptr_type>::type type;
};

template <class T>
struct deduce_storage_type
{
    typedef T type;
};

template <class T>
struct deduce_storage_type<T&>
{
    typedef T* type;
};

template <class T>
struct deduce_storage_type<const T&>
{
    typedef T type;
};

template <class T>
struct deduce_storage_type<T&&>
{
    typedef T type;
};

template <class T, class... Args>
struct filter_type;

template <class T, class Arg, class... Args>
struct filter_type<T, Arg, Args...>
{
    static constexpr bool pred = 
    std::is_same<typename undecorate<Arg>::type, T>::value;

    typedef typename deduce_storage_type<Arg>::type storage_type;

    typedef typename
    std::conditional<
        pred,
        typename sequence_cat<
            sequence<storage_type>,
            typename filter_type<T, Args...>::type
        >::type,
        typename filter_type<T, Args...>::type
    >::type type;       
};

template <class T, class Arg>
struct filter_type<T, Arg>
{
    static constexpr bool pred =
    std::is_same<typename undecorate<Arg>::type, T>::value;

    typedef typename deduce_storage_type<Arg>::type storage_type;

    typedef typename
    std::conditional<pred, sequence<storage_type>, void>::type
    type;
};

template <class... Args>
struct deduce_sequence_type
{
    typedef typename filter_type<char, Args...>::type char_sequence;
    typedef typename filter_type<int, Args...>::type int_sequence;
    typedef typename filter_type<float, Args...>::type float_sequence;

    typedef typename
    sequence_cat<
        char_sequence,
        typename sequence_cat<
            int_sequence,
            float_sequence
        >::type
    >::type type;
};

template <class T>
struct get_storage_type
{
    static T apply(T t) { return t; }
};

template <class T>
struct get_storage_type<T&>
{
    static T* apply(T& t) { return &t; }
};

template <class T>
struct get_storage_type<const T&>
{
    static T apply(const T& t) { return t; }
};

template <class T>
struct get_storage_type<T&&>
{
    static T&& apply(T&& t) { return std::move(t); }
};

template <class T, class Arg>
struct equals_undecorated_type
{
    static constexpr bool value =
    std::is_same<typename undecorate<Arg>::type, T>::value;
};

template <bool Pred, bool IsNextVoid, class T, class... Args>
struct filter_parameter_impl;

template <class T, class Arg1, class Arg2, class... Args>
struct filter_parameter_impl<false, false, T, Arg1, Arg2, Args...>
{
    typedef typename filter_type<T, Arg2, Args...>::type sequence_type;
    typedef typename sequence_type::tuple_type tuple_type;

    static constexpr bool pred = equals_undecorated_type<T, Arg2>::value;

    static constexpr bool is_next_next_void =
    std::is_same<typename filter_type<T, Args...>::type, void>::value;

    static tuple_type apply(Arg1&&, Arg2&& arg2, Args&&... args)
    {
        return filter_parameter_impl<
            pred, is_next_next_void, T, Arg2, Args...
        >::apply(
            std::forward<Arg2>(arg2),
            std::forward<Args>(args)...
        );
    }
};

template <class T, class Arg1, class Arg2, class... Args>
struct filter_parameter_impl<false, true, T, Arg1, Arg2, Args...>
{
    static void apply(Arg1&&, Arg2&&, Args&&...) {}
};

template <class T, class Arg1, class Arg2, class... Args>
struct filter_parameter_impl<true, false, T, Arg1, Arg2, Args...>
{
    typedef typename
    filter_type<T, Arg1, Arg2, Args...>::type
    sequence_type;

    typedef typename sequence_type::tuple_type tuple_type;

    static constexpr bool pred = equals_undecorated_type<T, Arg2>::value;

    static constexpr bool is_next_next_void =
    std::is_same<typename filter_type<T, Args...>::type, void>::value;

    static tuple_type apply(Arg1&& arg1, Arg2&& arg2, Args&&... args)
    {
        return std::tuple_cat(
            std::make_tuple(get_storage_type<Arg1>::apply(arg1)),
            filter_parameter_impl<
                pred, is_next_next_void, T, Arg2, Args...
            >::apply(
                std::forward<Arg2>(arg2),
                std::forward<Args>(args)...
            )
        );
    }
};

template <class T, class Arg1, class Arg2, class... Args>
struct filter_parameter_impl<true, true, T, Arg1, Arg2, Args...>
{
    typedef typename filter_type<T, Arg1>::type sequence_type;
    typedef typename sequence_type::tuple_type tuple_type;

    static tuple_type apply(Arg1&& arg1, Arg2&&, Args&&...)
    {
        return std::make_tuple(get_storage_type<Arg1>::apply(
            std::forward<Arg1>(arg1)
        ));
    }
};

template <class T, class Arg1, class Arg2>
struct filter_parameter_impl<false, false, T, Arg1, Arg2>
{
    typedef typename filter_type<T, Arg2>::type sequence_type;
    typedef typename sequence_type::tuple_type tuple_type;

    static tuple_type apply(Arg1&&, Arg2&& arg2)
    {
        return std::make_tuple(get_storage_type<Arg2>::apply(
            std::forward<Arg2>(arg2)
        ));
    }
};

template <class T, class Arg1, class Arg2>
struct filter_parameter_impl<false, true, T, Arg1, Arg2>
{
    static void apply(Arg1&&, Arg2&&) {}
};

template <class T, class Arg1, class Arg2>
struct filter_parameter_impl<true, false, T, Arg1, Arg2>
{
    typedef typename filter_type<T, Arg1>::type sequence_type;
    typedef typename sequence_type::tuple_type tuple_type;

    static tuple_type apply(Arg1&& arg1, Arg2&& arg2)
    {
        return std::make_tuple(
            get_storage_type<Arg1>::apply(std::forward<Arg1>(arg1)),
            get_storage_type<Arg2>::apply(std::forward<Arg2>(arg2))
        );
    }
};

template <class T, class Arg1, class Arg2>
struct filter_parameter_impl<true, true, T, Arg1, Arg2>
{
    typedef typename filter_type<T, Arg1, Arg2>::type sequence_type;
    typedef typename sequence_type::tuple_type tuple_type;

    static tuple_type apply(Arg1&& arg1, Arg2&&)
    {
        return std::make_tuple(
            get_storage_type<Arg1>::apply(std::forward<Arg1>(arg1))
        );
    }
};

template <class T, class... Args>
struct filter_parameter;

template <class T, class Arg, class... Args>
struct filter_parameter<T, Arg, Args...>
{
    typedef typename filter_type<T, Arg, Args...>::type sequence_type;

    typedef typename std::conditional<
        std::is_same<sequence_type, void>::value,
        void,
        typename sequence_type::tuple_type
    >::type tuple_type;

    static constexpr bool pred = equals_undecorated_type<T, Arg>::value;

    static constexpr bool is_next_void =
    std::is_same<typename filter_type<T, Args...>::type, void>::value;

    static tuple_type apply(Arg&& arg, Args&&... args)
    {
        return filter_parameter_impl<
            pred, is_next_void, T, Arg, Args...
        >::apply(std::forward<Arg>(arg), std::forward<Args>(args)...);
    }
};

template <bool Is1Void, bool Is2Void, bool Is3Void, class... Args>
struct get_tuple_impl;

template <class... Args>
struct get_tuple_impl<false, false, false, Args...>
{
    typedef typename deduce_sequence_type<Args...>::type sequence_type;
    typedef typename sequence_type::tuple_type tuple_type;

    static tuple_type apply(Args&&... args)
    {
        return std::tuple_cat(
            filter_parameter<char, Args...>::apply(
                std::forward<Args>(args)...
            ),
            filter_parameter<int, Args...>::apply(
                std::forward<Args>(args)...
            ),
            filter_parameter<float, Args...>::apply(
                std::forward<Args>(args)...
            )
        );
    }
};

template <class... Args>
struct get_tuple
{
    typedef typename deduce_sequence_type<Args...>::type sequence_type;

    typedef typename std::conditional<
        std::is_same<sequence_type, void>::value,
        void,
        typename sequence_type::tuple_type
    >::type tuple_type;

    static constexpr bool is1void =
    std::is_same<typename filter_type<char, Args...>::type, void>::value;
    static constexpr bool is2void =
    std::is_same<typename filter_type<int, Args...>::type, void>::value;
    static constexpr bool is3void =
    std::is_same<typename filter_type<float, Args...>::type, void>::value;

    static tuple_type apply(Args&&... args)
    {
        return get_tuple_impl<is1void, is2void, is3void, Args...>::
            apply(std::forward<Args>(args)...);
    }
};

template <class... Args>
struct foo
{
    typedef typename deduce_sequence_type<Args...>::type sequence_type;
    typedef typename sequence_type::tuple_type tuple_type;

    tuple_type t_;

    foo(Args&&... args) : t_{} {}
};

int main()
{
    char a = 5;
    const int b = 6;
    float c = 7;
    int d = 8;
    float e = 9;
    char f = 10;

    auto x = get_tuple<char&, const int&, float&, int&, float&&, char&>::
        apply(a, b, c, d, std::move(e), f);
    //std::tuple<char*, char*, int, int*, float*, float> x{&a, &f, b, &d, &c, std::move(f)};

    std::cout << typeid(x).name() << std::endl;

    return 0;
}

Чтобы воспроизвести результаты, сделайте следующее (при условии, что у вас установлено g++ - 4.7).

g++ -std=c++11 -Wall -Ofast -march=native variadic_reorder.cpp -S -o with_templates.s
// Comment out the line in main, and comment the line containing auto x, as well as the line below it.
g++ -std=c++11 -Wall -Ofast -march=native variadic_reorder.cpp -S -o without_templates.s
vimdiff with_templates.s without_templates.s

Единственные различия, которые я заметил, были такие вещи, как имена лейблов; в противном случае сгенерированный машинный код был идентичным.

Изменить 1 Я собираюсь принять свой собственный ответ, пока кто-то еще не напишет что-то более элегантное, чем то, что у меня есть.

Ответ 2

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

С другой стороны, менее вероятно, что они будут способствовать аргументам для регистров и обходить стек для простых функций. И ничто не гарантируется, пока кортеж используется только как чистая ценность (ссылка не указана), потому что в соответствии с правилом as-if нет необходимости, чтобы его члены имели какие-либо адреса.

Мы можем сохранить пространство стека, объединив указатели и сохранив более крупные выражения в конце кортежа.

Ссылки Lvalue реализованы в качестве указателей ABI, но вы указали их группировать как значения данных. Ссылки Rvalue следует рассматривать так же, как ссылки lvalue, если вы хотите придерживаться естественной семантики передачи. (Я предполагаю, что вы будете только перемещать типы классов.) Таким образом, проблема сортировки несколько сложнее, чем указано, потому что вы хотите сортировать параметры в значениях и категориях указателей, а затем сортировать их по базовому типу.

Что касается самого алгоритма сортировки, я бы попробовал просто выскочить из входного пакета и нажать на набор выходных кортежей, стиль очереди, а затем скомпоновать выходные кортежи с помощью std::tuple_cat. Это будет простейшим для реализации, стабильным и должно поразить оптимизацию в общем случае компилятора. Не реализуйте алгоритм, предназначенный для работы на месте в памяти, потому что TMP не работает так.

Что касается перевода результатов сортировки в функцию, которая переставляет параметры в аргументы forward_as_tuple, я не уверен. Вам, вероятно, придется иметь дело с индексами.

Вы хотите быть очень уверены, что это стоит того, прежде чем совершить все это.