Конструкторы C++ с 0/1 аргументом не связываются должным образом при передаче в конструктор шаблона

В приведенном ниже примере кода у меня есть класс с шаблонным конструктором с одним аргументом, который называется Acceptor1. Я хочу передать объекты типаFoo, Bar, or Bazк его конструктору, связывая вызовы конструктора. (На самом деле этот упрощенный пример отражает то, что я пытаюсь сделать со стиранием типа: где я хочу передать объект в класс ластика.) На самом деле происходит следующее: когда я пытаюсь объявитьAcceptor1 объект и передать его, например, Foo объект, я определяю переменную, которая указывает на функцию, которая принимает указатель FUNCTION (не Foo объект) и возвращает Acceptor1. Тип указателя функцииFoo(*)(). Общая переменная объявляется типаAcceptor1 (Foo (*)()).

Я не могу обойти это ни с каким возможным enable_if заявление, как показано Accessor2пример класса, где я даже не определил конструктор с одним аргументом.

Как я уже упоминал, конечная цель этого - иметь класс ластика типа, конструктор которого выглядит как Acceptor1, плюс дополнительная механика стирания шрифта. Я сделал это, и он отлично работает, пока тип, который я стираю, не построен с конструктором с 0/1 аргументом. Или я могу объявить и назначить переменные в двух отдельных инструкциях. Тем не менее, у меня их много, и это удваивает размер кода, плюс, я думаю, занимает еще несколько циклов процессора.

Есть ли у кого-нибудь идеи, как получить Acceptor1 test( Foo() ) рассматриваться как определение Accessor1, и инициализируя его Fooобъект? Он отлично работает сAccessor1 test( Baz(d, d2) ), ситуация с двумя аргументами. Он также отлично работает с литералами, напримерAcceptor1 test( Bar(1.0) ). Благодарность!

Шон

Вот код:

#include <iostream>
#include <typeinfo>

using namespace std;

struct Foo {
    Foo() {
        cout << "Foo" << endl;
    }
};

struct Bar {
    Bar(double a) {
        cout << "Bar" << endl;
    }
};

struct Baz {
    Baz(double a, double b) {
        cout << "Baz" << endl;
    }
};

struct Acceptor1 {
    template<typename T> //no possible enable_if could help this problem
    Acceptor1(T f) {
        cout << typeid(T).name() << endl;
    }
};

struct Acceptor2 {
};

int main()
{
    double d, d2;

    std::cout << std::endl;

    //0 arguments - captures a conversion constructor
    Acceptor1 one(Foo()); //F9Acceptor1PF3FoovEE=Acceptor1 (Foo (*)())
    cout << "one: " << typeid(one).name() << endl << endl;
    
    //0 arguments - the result we expect
    Acceptor1 two = Acceptor1(Foo()); //9Acceptor1=Acceptor1
    cout << "two: " << typeid(two).name() << endl << endl;

    //There's no possible way to enable_if it out - I deleted the whole constructor
    Acceptor2 three(Foo()); //F9Acceptor2PF3FoovEE=Acceptor2 (Foo (*)())
    cout << "three: " << typeid(three).name() << endl << endl;

    //1 arguments - captures a conversion constructor
    Acceptor1 four(Bar(d)); //F9Acceptor13BarE=Acceptor1 (Bar)
    cout << "four: " << typeid(four).name() << endl << endl;
    
    //1 arguments - the result we expect
    Acceptor1 five = Acceptor1(Bar(d)); //9Acceptor1=Acceptor1
    cout << "five: " << typeid(five).name() << endl << endl;

    //There's no possible way to enable_if it out - I deleted the whole constructor
    Acceptor2 six(Bar(d)); //F9Acceptor23BarE=Acceptor2 (Bar)
    cout << "six: " << typeid(six).name() << endl << endl;

    //1 arguments - literal
    Acceptor1 seven(Bar(5.0)); //9Acceptor1=Acceptor1
    cout << "seven: " << typeid(seven).name() << endl << endl;

    //2 arguments - the result we expect
    Acceptor1 eight(Baz(d, d2)); //9Acceptor1=Acceptor1
    cout << "eight: " << typeid(eight).name() << endl << endl;
    
    //2 arguments - the result we expect
    Acceptor1 nine = Acceptor1(Baz(d, d2)); //9Acceptor1=Acceptor1
    cout << "nine: " << typeid(nine).name() << endl << endl;

    using FooMaker = Foo(&)();
    using AcceptorFnToBazMaker = Acceptor1(*)(FooMaker); //PF9Acceptor1RF3FoovEE=Acceptor1 (*)(Foo (&)())
    cout << "AcceptorFnToBazMaker: " << typeid(AcceptorFnToBazMaker).name() << endl << endl;

    return 0;
}

РЕДАКТИРОВАТЬ: Было высказано предположение, что это дубликат конструктора по умолчанию с пустыми скобками - я согласен, что они оба упоминают "самый неприятный синтаксический анализ" как источник проблемы, но вопросы разные. Ответ там даже включает пример с функцией 2 параметров. В моем случае с конструкторами специально обрабатываются только конструкторы с 0/1 аргументом.

1 ответ

Решение

+ Изменить Acceptor1 test( Foo() ) к Acceptor1 test((Foo())) или даже лучше было бы Acceptor1 test{Foo()} или возможно (как и вы) Acceptor1 test = Acceptor1(Foo());

Компилятор может интерпретировать строку как действительное объявление функции и инициализацию объекта. В стандарте (насколько я помню) сказано, что в таком случае воспринимайте это как объявление функции. Вот что по этому поводу написано в cppreference:

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

И, следовательно, вы это получите.

Чтобы компилятор интерпретировал это как инициализацию объекта, добавьте паратесис ()вокруг, чтобы сделать его недопустимым объявлением функции. Визуально, все, что вам нужно сделать, это убедиться, что это недопустимый синтаксис объявления функции.

Другие вопросы по тегам