Есть ли подводные камни для имитации параметров функции по умолчанию с помощью агрегатной инициализации? - PullRequest
3 голосов
/ 17 октября 2019

Вопрос:

Если, например, вам нужна переменная функция, которая принимает произвольное число параметров Args&&...args и печатает все эти аргументы t раз. Более того, вы хотите, чтобы значение t по умолчанию было 1, поэтому по умолчанию он печатает все args один раз.

Первое, что вы попробуете:

template <typename ... Args>
void foo(Args... args, unsigned t = 1) {
    for (unsigned i = 0; i < t; ++i) {
        (std::cout << ... << args);
    }
}

Очевидно, что это не сработает, если вы не передадите явно параметры шаблона:

// Error: expected 1 argument, got 2
foo(0, "Hello, world!");

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

Затем я решил использовать агрегатную инициализацию (особенно обозначенный инициализатор начиная с c ++ 20) для имитации более мощного «параметра по умолчанию». Выглядит это так:

struct foo_t {
    unsigned t = 1;
    template <typename ... Args>
    void operator() (Args... args) {
        for (unsigned i = 0; i < t; ++i) {
            (std::cout << ... << args);
        }
    }
};

int main() {
    foo_t{}("Hello, ", "World!\n");      // prints 1 line
    foo_t{ 5 }(0, "Hello, world!\n");    // prints 5 lines
    return 0;
}

Более того, это может разрешить жалобу людей на то, что они не могут «пропустить» параметры функции по умолчанию с помощью с ++ 20 обозначенных инициализаторов:

struct bar_t {
    const std::string& str = "Hello, world!";
    int t = 1;
    void operator() () {
        for (int i = 0; i < t; ++i) {
            std::cout << str << std::endl;
        }
    }
};

int main() {
    // Skips .str, using the default "Hello, World!"
    bar_t{ .t = 10 }();
    return 0;
}

Мне интересно, есть ли какие-нибудь потенциальные ловушки, чтобы сделать это.

Справочная информация (может быть безопасно проигнорировано)

Итак, вчера я бродил по SO и столкнулся с вопросом (нопозже он был удален), который спрашивал о том, как объединить параметр std::source_location по умолчанию с шаблоном переменной:

template<typename... Args>
void log(Args&&... args, const std::experimental::source_location& location = std::experimental::source_location::current()) {
    std::cout << location.line() << std::endl;
}

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

struct logger {
    const std::experimental::source_location& location = std::experimental::source_location::current();
    template <typename... Args>
    void operator() (Args&&... args) {
        std::cout << location.line() << std::endl;
    }
};

int main(int argc, char** argv) {
    logger{}("I passed", argc, "arguments.");
    return 0;
}

Но обнаружил, что он может сделать больше, таким образом, этот вопрос.

1 Ответ

2 голосов
/ 17 октября 2019

Существует как минимум одна ловушка с временем жизни (расширением):

const std::string& str = "Hello, world!"; создать висячий указатель, (без расширения времени жизни для члена).

Следующее нормально:

void repeat_print(const std::string& str = "Hello, world!", int t = 1) {/*..*/}

int main()
{
    repeat_print();
}

, но следующее не так:

struct bar_t {
    const std::string& str = "Hello, world!";
    int t = 1;
    void operator() () const { /*..*/ }
};

int main()
{
    bar_t{}();
}

Вы можете исправить bar_t, чтобы получить член по значению, но в некоторых случаях вы будете делать дополнительное копирование.

...