Zażądano konwersji z lambda na typ nieskalarny

Dec 08 2020

Utworzyłem tę klasę, aby móc mieć wartość dowolnego typu, która jest stała lub ponownie obliczana za każdym razem, gdy jest używana:

template<typename T>
class Value {
    private:
    bool fixed;
    union {
        T value;
        std::function<T()> get;
    };
    public:
    Value(const T& value) : fixed(true), value(value) {}
    Value(const std::function<T()>& get) : fixed(false), get(get) {}
    Value(const T *pointer) : Value([pointer]() { return *pointer; }) {}
    ~Value() {}
    operator T() { return fixed ? value : get(); }
};

Wszystkie poniższe wyrażenia wydają się działać dobrze:

Value<double> a = 2.2;
double b = 1.;
double c = a;
Value<double> d = &b;
Value<int> e = Value<int>([]() { return 1.; });

Ale kiedy próbuję zrobić:

Value<double> f = []() { return 1.; };

wyzwalany jest błąd kompilacji:

error: conversion from 'main()::<lambda()>' to non-scalar type 'Value<double>' requested

Możesz wypróbować ten przykład tutaj .

Dlaczego przypisywanie działa dla Ti nie std::function<T()>i jak mogę to zrobić?

Uwaga: jestem świadomy tej odpowiedzi, ale nie zrozumiałem, jak rozwiązać problem bez konieczności jawnego wywoływania konstruktora, tak jak to zrobiłem Value<double> e.

Odpowiedzi

4 Slava Dec 08 2020 at 00:22

Dlaczego przypisywanie działa dla T, a nie std :: function <T ()> i jak mogę to zrobić?

Twój kod nie używa przypisania, ale kopiuje inicjalizację i

Ponadto niejawna konwersja w inicjalizacji kopiowania musi generować T bezpośrednio z inicjatora, podczas gdy np. Inicjalizacja bezpośrednia oczekuje niejawnej konwersji z inicjatora na argument konstruktora T.

Aby to zadziałało, musisz sprawić, aby Twój ctor bezpośrednio akceptował lambdę (to jest uproszczony przykład):

template<typename T>
class Value {
    std::function<T()> get;    
public:
    
    template<class Y>
    Value(Y lambda ) : get( std::move( lambda ) )  {}
};

kod na żywo Prawdopodobnie chcesz dodać ograniczenie za pomocą std::enable_ifkoncepcji lub, jeśli C ++ 20 jest dozwolone dla tego ctora, a także w tej formie ten konstruktor próbowałby zaakceptować wszelkie inne przeciążenia, które by nie powodowały i mogą powodować tajemnicze błędy. Zgodnie z tym parametrem szablonu enable_if jest lambda (z określonym podpisem) , może to być tak proste, jak

template<class Y, typename = decltype(std::declval<Y&>()())>
Value(Y lambda ) : get( std::move( lambda ) )  {}

który obsługuje C ++ 14. Oto kolejny przykład na żywo, w którym widać, że ten konstruktor nie jest używany do inicjowania typu int:

 Value<double> d2 = 123;

prog.cpp: 9: 5: uwaga: szablon kandydujący zignorowany: błąd podstawienia [z Y ​​= int]: wywołany typ obiektu 'int' nie jest funkcją ani wskaźnikiem funkcji Wartość (Y lambda): get (std :: move (lambda )) {}

4 NathanOliver Dec 08 2020 at 00:17

Lambda nie jest std::function. To znaczy, kiedy to robisz

Value<double> f = []() { return 1.; };

musisz przekonwertować []() { return 1.; }na konwersję std::function, która jest konwersją zdefiniowaną przez użytkownika, a następnie musisz ją przekonwertować std::functionna Value<double>inną konwersję zdefiniowaną przez użytkownika. To dwie konwersje zdefiniowane przez użytkownika, kiedy możesz tylko jedną taką konwersję. Dlatego kod nie może się skompilować.