Forum Replies Created

Viewing 15 posts - 106 through 120 (of 349 total)
  • Author
    Posts
  • in reply to: enclosing scope #629096
    RainerRainer
    Keymaster
        Up
        1
        Down
        ::

        Enclosing scope is not an official term. I use it for the lack of words. The enclosing scope is the scope around a scope. Here is a small example:

        enum class Color{
          red,
          blue,
          green
        };
        
        Color col = Color::red;
        
        void func() {
        
            enum class Color2{
                red,
                blue,
                green
            };
            Color2 col = Color2::red;
        
        }
        
        // Color2 col2 = Color2::red; // ERROR (1)
        
        int main(){
        
            func();
        
            Color col = Color::red;
            // Color2 col2 = Color2::red;  // ERROR (2)
        
        }
        

        You can use Color in the entire program, because it’s defined outside any scope. Color has so-called static storage duration. On the contrary, Color2 cannot be used outside of main (line 1), or in the enclosing stop (line 2).

        in reply to: Static_assert fails within consteval function #629080
        RainerRainer
        Keymaster
            Up
            0
            Down
            ::

            Arguments to consteval (constexpr also) are not automatically constexpr.

            On the contrary, when C++ would permit constexpr functions arguments, this would have serious consequences:

            constexpr int func1(constexpr int x) {  // ERROR
                return x;
            }
            
            consteval int func2(constexpr int x) { // ERROR
                return x;
            }
            
            int main() {
                func1(2011);
                func2(2020);
            }
            

            The compiler would have to create for each call of func1 and func2 a new instantiation, similar to function template calls.

            The following program exemplifies the difference.

            consteval int factorial(int  n) {
                if ( n == 1 ) return 1;
                else return n * factorial(n - 1);
            }
            
            template <int N>
            struct Factorial{
                static int const value= N * Factorial<N-1>::value;
            };
            
            template <>
            struct Factorial<1>{
                static int const value = 1;
            };
            
            int main() {
              
                factorial(5);
                Factorial<5>;
                  
            }
            

            The call Factorial(5) triggers the instantiation of Factorial<5> to Factorial<2>. On the contrary, there is only one consteval function factorial. You can study the output of the program in C++ Insights: https://cppinsights.io/s/deea83ed. The corresponding argumentation would hold for a constexpr function, executed at compile time.

             

             

            RainerRainer
            Keymaster
                Up
                1
                Down
                ::

                There are a few issues in your program:

                template <typename T>
                class MyClass{
                
                public:
                
                    MyClass(T t, double d) :  myField_(t), myDouble(d){}
                
                private:
                
                    T myField_;
                
                    double myDouble;
                
                };
                
                // Specialization
                
                template <>
                class MyClass<int>{
                
                public:
                
                    MyClass(int a) : myField_(a){}
                
                private:
                
                    int myField_;
                
                };
                
                int main () {
                
                    MyClass<double> mcClass(5.5, 10.5);
                    MyClass<int> myClass2(5);
                
                }
                

                The constructor or a member function of a derived class are not related to the same member functions in the base class. They can, therefore, vary, but this may not be ideal for the usability of the class.
                The compiler choose the appropriate. The compiler prefers a partial specialization or full specialization, if the arguments of the class are a subset of the template arguments.

                The compiler applies the best match on the template parameter.

                in reply to: definitions inisde or outside template class #628922
                RainerRainer
                Keymaster
                    Up
                    1
                    Down
                    ::

                    In general, there is no difference if you define a member function inside or outside the class. Here are only a few remarks.

                      • When you define a member function inside a class, it is implicitly declared as inline. inline is a hint for the compiler to inline this member function call.
                      • Definen a member function of a class templates or, in particular, a generic member function of class templates outside the class is pretty complicated. Conside the following example
                    • A generic copy assignment operator defined inside the class:

                    template <class T, int N>
                    class Array{ 
                    public: 
                      template <class T2>
                      Array<T, N>& operator = ( 
                    ...
                    
                    • A generic copy assignment operator declared inside the class but defined outside.

                    template <class T, int N>
                    class Array{ 
                    public: 
                      template <class T2>
                      Array<T, N>& operator = (const Array<T2, N>& a); 
                    ...
                    };
                    template<class T, int N>
                      template<class T2>
                       Array<T, N>& Array<T, N>::operator = (const Array<T2, N>& a{
                        ...
                    
                    RainerRainer
                    Keymaster
                        Up
                        0
                        Down
                        ::

                        Let me clarify: Thanks to ThreadSanitizer (command line flags: -fsanitize=thread -g), you can visualize the data race on inline static int mem{}.Take a look here: https://godbolt.org/z/ch1naTTTq.

                        When you modify the static inside a synchronized member function, this is, as you said, still a data race (https://godbolt.org/z/foTxq741M), because each instance of Critical has is own mutex. To get ride of the data race, all instances of Critical would have to use the same mutex. We need in this case synchronization on the class level, but not on the object level.

                        RainerRainer
                        Keymaster
                            Up
                            0
                            Down
                            ::

                            Here are two ideas for reference semantics:

                            1. A container should have polymorphic types, and you don’t like pointers.
                            2. You can reference something outside the container that addresses an element inside the container.

                            The following program applies both ideas:

                            #include <functional>
                            #include <iostream>
                            #include <vector>
                            
                            class Ball {
                             public:
                                virtual void getName(){ 
                                    std::cout << "Ball\n";
                                }
                                virtual ~Ball() {}
                            };
                             
                            class HandBall: public Ball {
                                void getName() override {
                                    std::cout << "HandBall\n";
                                }
                            };
                            
                            class BasketBall: public Ball {
                                void getName() override {
                                    std::cout << "BasketBall\n";
                                }
                            };
                            
                            
                            int main() {
                            
                                Ball* ball = new Ball{};
                                Ball* handBall = new HandBall{};
                                Ball* basketBall = new BasketBall{};
                            
                                std::vector<Ball*> myVec1{ball, handBall, basketBall};
                            
                                for (auto b: myVec1) b->getName();
                            
                                delete ball;
                                delete handBall;
                                delete basketBall;
                            
                                std::cout << '\n';
                            
                                Ball ball1;
                                HandBall handBall1;
                                BasketBall basketBall1;
                            
                                std::vector<std::reference_wrapper<Ball>> myVec2{ball1, handBall1, basketBall1};
                                for (auto b: myVec2) b.get().getName();
                            
                                std::cout << '\n';
                            
                                std::vector<std::reference_wrapper<int>> myVec3;
                                int a{0};
                                int b{1};
                                int c{-2};
                            
                                myVec3.push_back(a);
                                myVec3.push_back(b);
                                myVec3.push_back(c);
                            
                                for (auto v: myVec3) std::cout << v << ' ';
                            
                                c = 2;
                                std::cout << '\n';
                            
                                for (auto v: myVec3) std::cout << v << ' ';
                            
                            }
                            

                            Line (1) is a std::vector<std::reference_wrapper<Ball>, and line (2) modifies c outside the container that also addresses the element inside the vector. You have the program here: https://godbolt.org/z/WP39rs5v9.

                            in reply to: Factory method as static member of base class #628897
                            RainerRainer
                            Keymaster
                                Up
                                0
                                Down
                                ::

                                Now, I have it.

                                The function createWindow(std::unique_ptr<Window>& oldWindow) should create a new Window as given, manged by a std::unique_ptr. Taking a std::unique_ptr<Widget> by value does not work. Your only two options are a reference or a pointer.

                                When you have the guarantee that the argument is no null pointer, use a reference. This guarantee is provided here.

                                Additionally, a function taking a pointer looks strange:

                                auto createWindow2(std::unique_ptr<Window>* oldWindow) { 
                                    return (*oldWindow)->create();
                                }
                                

                                You can study all three variations (reference, pointer, value) on goldbolt: https://godbolt.org/z/4YoE1nvxK.

                                in reply to: vectorArithmeticOperatorOverloading.cpp #628794
                                RainerRainer
                                Keymaster
                                    Up
                                    0
                                    Down
                                    ::

                                    You are right. Change the s <= a.size() to s < a.size().

                                    in reply to: Idiomatic implementation of Type Erasure #628783
                                    RainerRainer
                                    Keymaster
                                        Up
                                        0
                                        Down
                                        ::

                                        Here is the answer from Klaus Igelberger.

                                        The idiomatic, correct solution is given by C.20: “If you can avoid defining default operations, do”. This is the guideline that we also know as the “rule of zero”.

                                        The derived ‘Model’ class can be copied on its own – no slicing would occur. ‘Model’ is a value type, whose default implementations of the special members will work. Therefore there is no point in providing them, this would only make the class unnecessarily complex. This argument is strengthened by the fact that in a real type erasure implementation ‘Model’ will always be used via pointer to ‘Concept’ and therefore will not be copied directly, but only via the ‘clone()’ member function.

                                        in reply to: Factory method as static member of base class #628782
                                        RainerRainer
                                        Keymaster
                                            Up
                                            0
                                            Down
                                            ::

                                            Can you provide more information? Returning by reference means that you borrow something. On the contrary, with a std::unique_ptr you want to model ownership. My first thought is, therefore, returning a std::unique_ptr by reference makes no sense.

                                            You should either return a std::unique_ptr or a std::shared_ptr.

                                            in reply to: Idiomatic implementation of Type Erasure #628779
                                            RainerRainer
                                            Keymaster
                                                Up
                                                0
                                                Down
                                                ::

                                                Here is the answer from Arthur O’Dwyer.

                                                Your type-erasure question seems to start from the wrong end. As I say in my “Back to Basics: Type Erasure,” when you’re planning to write a type-erasure type, you should always start by making a list of affordances — things that you’ll need to do with the object. In your example, the object is the type you’re calling Wrapper. This immediately creates a problem for you: It’s totally unclear what kinds of things you expect to be able to do with a “Wrapper“!
                                                In my examples, I always use concrete names like “Printable“, “Callable“, “UniquePrintable“, “Number“, etc. That way, the programmer has a head start on figuring out what the affordances of that type ought to be. For example, a “Printable” ought to be print-able. And perhaps copyable as well:

                                                class Printable {
                                                 public:
                                                    explicit(false) Printable(T t) : [...] { }
                                                    Printable(const Printable& rhs) : [...] { }
                                                    Printable& operator=(Printable rhs) : [...] { }
                                                    ~Printable() = default;
                                                    friend std::ostream& operator<<(std::ostream& os, const Printable& self) {
                                                            [...]
                                                        }
                                                 private:
                                                    [...]
                                                };
                                                

                                                Now that we’ve written down the shape of our Printable class, we can proceed to implement it.
                                                This second step is where you started, and that’s why you had such trouble.

                                                Another naming issue: I would call the implementation-detail classes “Printable::Base” and “Printable::Derived“. You decided to call them “Concept” and “Model“. That’s kind of the right idea, but I think it’s highly likely to confuse the reader, because what we are doing here has nothing to do with C++20 concepts or modeling concepts! Specifically, your class Concept is not a concept, and your class Model does not model anything.  Your class Concept is what we might call an “interface” and class Model is an “implementation” of that interface… but personally I wouldn’t even use those names, because “interface” often connotes “public API,” and our base class here is not providing a public API; the whole thing is a private implementation detail of Printable. Users will interact only with Printable.  Therefore, I think “Base” and “Derived” are the only sensible names. They are true (unlike Concept/Model) and they are appropriately unobtrusive.

                                                Okay, so, we need a Base and a Derived. You got confused by your “Model” class because you hadn’t done the first step — i.e. to figure out what it needs to do — and so your “Model” class contains some extraneous cruft.

                                                Model(T d, int i, std::string s) : data{d}, idx{i}, str{s} { }
                                                

                                                This constructor is never going to be called. The only constructor that our Derived<T> class should have, is Derived(T t).
                                                Writing the Base and Derived classes’ vtables from our list of affordances is usually straightforward. For Printable::Base, we know we need a virtual function to print the thing, and we know we need a way to copy it.

                                                struct Base {
                                                    virtual void do_print(std::ostream&) const = 0;
                                                    virtual std::unique_ptr<Base> do_clone() const = 0;
                                                    virtual ~Base() = default;
                                                };

                                                Derived<T> simply wraps a T object and implements those methods. (Hmm, “wraps” a T… This suggests another reason that your choice of the name Wrapper for Printable was a bad one!)

                                                template<class T>
                                                struct Derived {
                                                    explicit Derived(T t) : t_(std::move(t)) {}
                                                    void do_print(std::ostream& os) const override { os << t_; }
                                                    std::unique_ptr<Base> do_clone() const override { return std::make_unique<Derived>(t_); }
                                                    T t_;
                                                };
                                                

                                                And that’s it! That’s the entire implementation!

                                                Notice that in my traditional style, I don’t bother to =delete the value-semantic operations of Base or Derived. There’s no reason to =delete them; I simply won’t ever try to call them. (And I don’t have to worry about other people trying to call them, because both of these classes are private implementation details. Nobody else has access to them.) But, if you wanted to, you could add =delete‘d value-semantic operations:

                                                struct Base {
                                                    explicit Base() = default;  // this becomes necessary as soon as you provide any other ctor (e.g. the deleted copy ctor below)
                                                    Base(const Base&) = delete;  // unnecessary, but fine
                                                    Base& operator=(const Base&) = delete;  // unnecessary, but fine
                                                    virtual void do_print(std::ostream&) const = 0;
                                                    virtual std::unique_ptr<Base> do_clone() const = 0;
                                                    virtual ~Base() = default;
                                                };
                                                

                                                I prefer not to do this, because it turns a 5-line class into an 8-line class and serves no physical purpose.

                                                Actually, re-reading your email… You wrote:
                                                > it isn’t clear to me how we should define the copy and move operations for the derived class Model.

                                                This seems to show a confusion between what it means to be a polymorphic class like Derived, versus a value-semantic class like Printable.

                                                Polymorphic types in C++ are never value-semantic. They can’t be copied, moved, swapped, sorted, etc.

                                                void test(Animal& a, Animal& b) {
                                                    std::swap(a, b);  // Wrong! Won't work; shouldn't compile!
                                                    a = b;  // Wrong! Won't work; shouldn't compile!
                                                }
                                                

                                                That’s because “swap” means “take the animal referred to by a, and put it into the animal referred to by b; and vice versa.” We can do that for a value-semantic type like int, because all int’s are the same “shape” (the same size). We can’t do that for Animal, because animals come in many different shapes [poly-morphic, get it?] and sizes. If a refers to a Mouse and b refers to an Elephant, we’re just out of luck: you can’t put an Elephant into the footprint of a Mouse.

                                                in reply to: Idiomatic implementation of Type Erasure #628777
                                                RainerRainer
                                                Keymaster
                                                    Up
                                                    0
                                                    Down
                                                    ::

                                                    Good point. Let me broaden the discussion and ask Klaus Iglberger and Arthur O’Dwyer. I will add their answers to this thread.

                                                    in reply to: Type deduction system using type erasure #628758
                                                    RainerRainer
                                                    Keymaster
                                                        Up
                                                        0
                                                        Down
                                                        ::

                                                        Type erasure allows you to deal with unrelated types in a uniform way. A nice example is std::function. std::function is a polymorphic function wrapper. It can store any callable that fulfills the signature. Here is an example of std::function implementing a dispatch table.

                                                        // dispatchTable.cpp
                                                        
                                                        #include <cmath>
                                                        #include <functional>
                                                        #include <iostream>
                                                        #include <map>
                                                        
                                                        int main(){
                                                        
                                                          std::cout << std::endl;
                                                        
                                                          // dispatch table
                                                          std::map< const char , std::function<double(double,double)> > dispTable{
                                                            {'+',[](double a, double b){ return a + b;} },
                                                            {'-',[](double a, double b){ return a - b;} },
                                                            {'*',[](double a, double b){ return a * b;} },
                                                            {'/',[](double a, double b){ return a / b;} } };
                                                        
                                                          // do the math
                                                          std::cout << "3.5+4.5= " << dispTable['+'](3.5,4.5) << std::endl;
                                                          std::cout << "3.5-4.5= " << dispTable['-'](3.5,4.5) << std::endl;
                                                          std::cout << "3.5*4.5= " << dispTable['*'](3.5,4.5) << std::endl;
                                                          std::cout << "3.5/4.5= " << dispTable['/'](3.5,4.5) << std::endl;
                                                        
                                                          // add a new operation
                                                          dispTable['^']=  [](double a, double b){ return std::pow(a,b);};
                                                          std::cout << "3.5^4.5= " << dispTable['^'](3.5,4.5) << std::endl;
                                                        
                                                          std::cout << std::endl;
                                                        
                                                        };
                                                        

                                                        Additionally, this technique is type safe. Using a callable having the wrong signature would cause a compile time error. In contrast to templates, this error message is pretty readable.

                                                        Another examples of type erasure in C++ are std::any, the polymorphic allocators (pmr), the allocators of std::shared_ptr, …

                                                        in reply to: Advance implementation in C++98 vs. C++17 #628739
                                                        RainerRainer
                                                        Keymaster
                                                            Up
                                                            0
                                                            Down
                                                            ::

                                                            Tag dispatching is often used in the Standard Template Library. Often you dispatch on std::true_type or std::false_type (std::integral_constant).

                                                            Here is a simplified version of std::fill: The Type-Traits Library: Optimization.

                                                            #include <cstring>
                                                            #include <chrono>
                                                            #include <iostream>
                                                            #include <type_traits>
                                                            
                                                            namespace my{
                                                            
                                                              template <typename I, typename T, bool b>
                                                              void fill_impl(I first, I last, const T& val, const std::integral_constant<bool, b>&){
                                                                while(first != last){
                                                                  *first = val;
                                                                  ++first;
                                                                }
                                                              }
                                                            
                                                              template <typename T>                                                              
                                                              void fill_impl(T* first, T* last, const T& val, const std::true_type&){
                                                                std::memset(first, val, last-first);
                                                              }
                                                            
                                                              template <class I, class T>
                                                              inline void fill(I first, I last, const T& val){
                                                                typedef std::integral_constant<bool,std::is_trivially_copy_assignable<T>::value 
                                                                        && (sizeof(T) == 1)> boolType;                                           
                                                                fill_impl(first, last, val, boolType());
                                                              }
                                                            }
                                                            
                                                            const int arraySize = 100'000'000;
                                                            char charArray1[arraySize]= {0,};
                                                            char charArray2[arraySize]= {0,};
                                                            
                                                            int main(){
                                                            
                                                              std::cout << '\n';
                                                            
                                                              auto begin = std::chrono::steady_clock::now();
                                                              my::fill(charArray1, charArray1 + arraySize,1);
                                                              auto last =  std::chrono::steady_clock::now() - begin;
                                                              std::cout <<  "charArray1: " << std::chrono::duration<double>(last).count() << " seconds\n";
                                                            
                                                              begin = std::chrono::steady_clock::now();
                                                              my::fill(charArray2, charArray2 + arraySize, static_cast<char>(1));
                                                              last=  std::chrono::steady_clock::now() - begin;
                                                              std::cout <<  "charArray2: " << std::chrono::duration<double>(last).count() << " seconds\n";
                                                            
                                                              std::cout << '\n';
                                                            
                                                            }
                                                            
                                                            in reply to: Strategy with unique_ptr #628733
                                                            RainerRainer
                                                            Keymaster
                                                                Up
                                                                1
                                                                Down
                                                                ::

                                                                Your implementation is fine.

                                                                • When you model exclusive ownership, use std::unique_ptr.
                                                                • When you model shared ownership, use std::shared_ptr.
                                                              Viewing 15 posts - 106 through 120 (of 349 total)