u/alfps

198 post karma · 15.1k comment karma · joined Sep 2011

alfps

❞ how cna I use int and string in loops while also printing the number required

That's an extremely vague and broad question, but since you are a beginner I will try to build up from what a beginner needs first.


I think this is about conversion int → string and, conversely, string → int.

The C++ standard library supports such conversions via several sub-libraries, and generally you will choose to work with one of them:

  • the old C standard library with e.g. sprintf and sscanf;

  • the C++ string library with e.g. to_string and stoi;

  • the C++ iostreams library with ostringstream and istringstream; and

  • the “modern C++” library functions with format and from_chars.

Additionally since these conversion from and to int type are not very difficult to code up, you may at times just code your own

  • DIY functions,
    totally from scratch or as wrappers around the standard library's functionality.

Reasons that you may code up DIY wrappers include to get your own preferred failure handling, and/or that you may wish to support special notations or avoid that a national notation is used via a “locale”, and/or that you're most interested in the digit values and not the string representation.


As an example of using the modern format function for int → string conversion, consider a program to list the palindromic numbers in the range 1 through 123:

#include <format>
#include <print>
#include <string>
#include <string_view>
using   std::format, std::print, std::string, std::string_view;

auto is_palindrome( const string_view ascii_string )
    -> bool
{
    // Using ranges it's just `return equal( reverse_view( ascii_string ), ascii_string );`.
    int i_right     = int( ascii_string.size() ) - 1;
    int i_left      = 0;
    while( i_left < i_right ) {
        if( ascii_string[i_left] != ascii_string[i_right] ) {
            return false;
        }
        ++i_left;  --i_right;
    }
    return true;
}

auto main() -> int
{
    const int n = 123;
    for( int i = 1; i <= n; ++i ) {
        const string decimal = format( "{:d}", i );         // Conversion `int` → `string`.
        if( is_palindrome( decimal ) ) {
            print( "{:s} ", decimal );
        }
    }
    print( "\n" );
}

Output:

1 2 3 4 5 6 7 8 9 11 22 33 44 55 66 77 88 99 101 111 121

Generally you don't have to specify the intended conversion like :d and :s above because it's usually the default, but I used explicit specifications to exemplify how to do it.


Going the other way, string → int, yields the possibility of conversion failure. from_chars reports failure via its function return result, which is a low level C-ish design. That means that one will naturally write a DIY wrapper for it.

But first let's use it directly:

#include <charconv>
#include <format>
#include <iostream>
#include <print>
#include <string>
#include <string_view>
using   std::from_chars, std::from_chars_result, std::errc,
        std::format, std::cin, std::print, std::string, std::getline, std::string_view;

#include <cstdlib>      // Also provides the `EXIT_FAILURE` macro constant.
using   std::exit;

void fail( const string_view message )
{
    print( stderr, "!{}.\n", message );
    exit( EXIT_FAILURE );
}

auto input_line() -> string
{
    string line;
    getline( cin, line );
    if( cin.fail() ) { fail( "Failed to input a line of text." ); }
    return line;
}

auto message_for( const errc code ) -> string
{
    switch( code ) {
        case errc::invalid_argument:        return "must be like e.g. “42” or “-3”";
        case errc::result_out_of_range:     return "the value is too large";
        default: {}
    }
    return format( "errc value {:d}", int( code ) );
}

auto main() -> int
{
    for( ;; ) {
        print( "Please type an integer, or just return to quit: " );
        const string input = input_line();
        if( input.empty() ) {
            break;
        }
        int value;
        using Char_ptr = const char*;
        const Char_ptr p_first      = input.data();
        const Char_ptr p_beyond     = p_first + input.size();
        const from_chars_result result = from_chars( p_first, p_beyond, value );
        if( result.ec != errc() ) {
            print( "Sorry, “{}” was not a valid integer: {}.\n", input, message_for( result.ec ) );
        } else if( result.ptr != p_beyond ) {
            print( "Sorry, there was extraneous text after what could be an intended integer.\n" );
        } else {
            print( "Thanks, you specified the value {:d}.\n", value );
        }
        print( "\n" );
    }
}

Possible interaction:

Please type an integer, or just return to quit: asd
Sorry, “asd” was not a valid integer: must be like e.g. “42” or “-3”.

Please type an integer, or just return to quit: 32q3
Sorry, there was extraneous text after what could be an intended integer.

Please type an integer, or just return to quit:  2
Sorry, “ 2” was not a valid integer: must be like e.g. “42” or “-3”.

Please type an integer, or just return to quit: 1
Thanks, you specified the value 1.

Please type an integer, or just return to quit: ⏎

std::expected is a more reasonable result type for a string → int conversion function. An instance of expected carries either a value of the desired result type, or a value of some other type that typically specifies what went wrong. For a from_chars wrapper that other type can be an errc:

#include <charconv>
#include <expected>
#include <format>
#include <iostream>
#include <print>
#include <string>
#include <string_view>
using   std::from_chars, std::from_chars_result, std::errc,
        std::expected, std::unexpected,
        std::format, std::cin, std::print, std::string, std::getline, std::string_view;

#include <cstdlib>      // Also provides the `EXIT_FAILURE` macro constant.
using   std::exit;

void fail( const string_view message )
{
    print( stderr, "!{}.\n", message );
    exit( EXIT_FAILURE );
}

auto input_line() -> string
{
    string line;
    getline( cin, line );
    if( cin.fail() ) { fail( "Failed to input a line of text." ); }
    return line;
}

auto message_for( const errc code ) -> string
{
    switch( code ) {
        case errc::invalid_argument:        return "must be like e.g. “42” or “-3”";
        case errc::result_out_of_range:     return "the value is too large";
        case errc::argument_list_too_long:  return "extraneous text at the end";
        default: {}
    }
    return format( "errc value {:d}", int( code ) );
}

auto int_from( const string_view spec ) -> expected<int, errc>
{
    int value;
    using Char_ptr = const char*;
    const Char_ptr p_first      = spec.data();
    const Char_ptr p_beyond     = p_first + spec.size();
    const from_chars_result result = from_chars( p_first, p_beyond, value );
    if( result.ec != errc() ) {
        return unexpected( result.ec );
    } else if( result.ptr != p_beyond ) {
        // Extraneous text after spec.
        return unexpected( errc::argument_list_too_long );      // Probably closest in meaning.
    }
    return value;
}

auto main() -> int
{
    for( ;; ) {
        print( "Please type an integer, or just return to quit: " );
        const string input = input_line();
        if( input.empty() ) {
            break;
        }
        const expected<int, errc> result = int_from( input );
        if( not result.has_value() ) {
            print( "Sorry, “{}” was not a valid integer: {}.\n", input, message_for( result.error() ) );
        } else {
            print( "Thanks, you specified the value {:d}.\n", result.value() );
        }
        print( "\n" );
    }
}

This does not produce the exact same response for user input with text after the integer.

That could be arranged, at the cost of extra code to deal with that case.

But as is the program produces e.g.

Please type an integer, or just return to quit: asd
Sorry, “asd” was not a valid integer: must be like e.g. “42” or “-3”.

Please type an integer, or just return to quit: 32q3
Sorry, “32q3” was not a valid integer: extraneous text at the end.

Please type an integer, or just return to quit: 2
Thanks, you specified the value 2.

Please type an integer, or just return to quit: ⏎
alfps

❞ people will scratch their head when they see it.

Oh, unless you're referring to the editing/typing error, those super-challenged people should not be allowed within 300 m distance of any C++ code.

alfps

❞ I don't have a computer

You need one.

alfps

Oh I forgot to address the correctness of the output operation itself. Sorry.

When you pass a const char* to println it assumes that this is a zero-terminated string.

  • The file may have zero-bytes, in which case the output will be truncated.
  • If the file doesn't have a zero byte at the end, println will read beyond the vector buffer, with UB as result.

So you need to use an output operation where the number of bytes is specified, instead of relying on null-termination.

As I recall cat in Unix handles null-bytes just fine, by copying them, so you can use cat to e.g. copy a binary picture file.

alfps

The source code:

#include <cstdio>
#include <fcntl.h>
#include <print>
#include <sys/stat.h>
#include <stdlib.h>
#include <unistd.h>
#include <vector>


int main(int words_num,char** words){
  if (words_num < 1){std::println("missing file path");return -1;}
  int fd =open(words[1],O_RDONLY);
  if (fd==-1){std::println("failed to open the fille");perror("open");return -1;};
  struct stat fd_st{};
  fstat(fd,&fd_st);
  std::vector<char>buf{};
  buf.resize(fd_st.st_size);
  if (read(fd,buf.data(),fd_st.st_size)==-1){std::println("failed to read the file");perror("read");return -1;};
  std::println("{}",buf.data());
  close(fd);

}

General observations:

  • Some blank lines that don't contribute to clarity; that's somewhat annoying.
    Just remove the extra blank lines.

  • Unix-specific for premature optimization, i.e. needlessly non-portable.
    Using the FILE* API gets you buffering and portability.

  • 2 spaces indent like in HTML/CSS fouls up indenting operations in e.g. VS Code.
    4 spaces is a good convention. Works with VS Code and other modern editors.


if (words_num < 1) {
    std::println("missing file path");
    return -1;
}

Here words_num is the traditional argc parameter of main. For an ordinary invocation without command line arguments this is 1. So it is in practice 1 or more so the if condition will always be false.

The error message should better be presented on the process' standard error stream, and have a newline at the end, like this:

std::println( stderr, "missing file path\n" );

The return -1; from main will work in practice both in Windows, Unix and VMS, but it's needlessly non-standard. Better use EXIT_FAILURE from the <cstdlib> header. I.e.

return EXIT_FAILURE;

if (fd==-1) {
    std::println("failed to open the fille");
    perror("open");
    return -1;
};

Same comments as above, i.e. should use error stream, should have newline, should use EXIT_FAILURE constant, but additionally this code mixes error output to standard output and standard error streams.

Just use stderr for error messages.

The reason the error stream was invented was that a wayward process that presented error messages on the then one and only output stream, really fouled up the actions of a process that this text was piped to. Which reacted by doing very undesired things. With error messages on a separate error stream the consequences of a failure will usually be more benign / less catastrophe-like.


std::vector<char>buf{};
buf.resize(fd_st.st_size);

This does a dynamic allocation. Dynamic allocations are generally expensive, and with a large file you are not guaranteed that the file fits in memory. These are two reasons why file reading is usually done with fixed size buffers one chunk at a time, like FILE* operations.


std::println("{}",buf.data());
close(fd);

Better check that the println call succeeded. E.g. the process' standard output stream might be redirected to a file that it doesn't have write access to. Whatever.

std::println works as if it delegates to std::print, which throws an exception on failure.

So the way to check for failure is like

try {
    std::println("{}",buf.data());
} catch( const std::exception& x ) ) {
    std::fprintf( stderr, "!Failed to write the output - %s.\n", x.what() );
    return EXIT_FAILURE;
}
alfps

Formatted:

#include <iostream>
using namespace std;
int main()
{
    long long n, sum = 0;
    if (!(cin>>n)) return 0;
    for (int i = 1; i * i <= n; i++) {
        if(n%i == 0) {
            if(i%2 != 0) {
                sum = sum+i;
            }
            int pair = n/i;
            if (pair!=i && pair %2 != 0) {
                sum = sum + pair;
            }
        }
    }
    cout<<sum;
}

There are two updates of sum. Try removing one of them and figure out that program. Ditto for the other.

Pro tip: to increase your chances of getting good help post formatted code (extra-indent it with 4 spaces) and do not demand help.

alfps

❞ The value of counter must be nonnegative.

This is under-specified.

It should have told you the expected reaction to a negative value:

  • clamp it to 0, or
  • throw an exception, or
  • assert that it's not negative (termination in debug build).

A reasonable guess is that the intent is a clamped value where any negative value becomes 0. It's reasonable because it lets the shown main work as a test.

Most probably the code will pass when you add that clamping in the constructor, in the value setter and in the decrement operation.


The required design of this class is horrible and teaches you ungood things.

In particular the value setter defeats the whole purpose of the class. When you have public "get value" and "set value" operations that leaves the value completely open as if it were just a public member. Then it cannot be relied on.

Another serious problem is having print as a method of the class. That makes that functionality unusable in a GUI. Never do i/o in a class unless that's what the class is specifically about.

And all the "get" prefixes. That's a Java-ism with no advantage in C++, but it does have costs:

  • it uses up a good convention where "get" indicates a costly doer action, and
  • it adds needless verbosity.

Re the verbosity, think about getSinOf(x)*getCosOf(x). It's so silly and counter productive that it's over in retard-land. Don't do that.

So don't use "get" prefixes in your own code, except where there really is a "get"ter action.


You are however forced to use e.g. "get" and having printing as a method.

But you can at least add const to the operations that don't change the value, e.g.

class counterType
{
    int counter;

public:
    counterType();
    counterType(int count);

    void print() const;
    int getCounter() const;
    void incrementCounter();
    void decrementCounter();
    void setCounter(int num);
};

If you were free to do this class in a more reasonable way then also the redundancy in the name of the class would be stripped off, like

#include <algorithm>

class Counter
{
    int m_value;

public:
    Counter(): m_value() {}
    Counter( const int value ): m_value( std::max( 0, count ) ) {}

    Counter& operator=( const Counter& ) = delete;      // No value assignment!

    int value() const { return m_value; }
    // void setCounter(int num) -- removed

    void increment() { ++m_value; };
    void decrement() { if( m_value > 0 ) { --m_value; } }
};

#include <iostream>
void print( const Counter& counter ) { std::cout << counter.value(); }
alfps

To post code with formatting just extra-indent the code with 4 spaces.

No triple backticks.

Here's the source code formatted in Stroustrup style by AStyle, and so presented:

#include <SFML/Graphics.hpp>
#include <time.h>
#include <cstdlib>

using namespace sf;

int N = 30, M = 20;
int size = 16;
int w = size * N;
int h = size * M;
int num = 4;
int dir = 0;

struct Snake {
    int x, y;
} s[100];

struct Fruit {
    int x, y;
} f;

void Tick()
{
    for (int i = num - 1; i > 0; i--) {
        s[i].x = s[i - 1].x;
        s[i].y = s[i - 1].y;
    }
    if (dir == 0)
        s[0].y += 1;
    if (dir == 1)
        s[0].x -= 1;
    if (dir == 2)
        s[0].x += 1;
    if (dir == 3)
        s[0].y -= 1;
    if (s[0].x >= N)
        s[0].x = 0;
    if (s[0].x < 0)
        s[0].x = N - 1;
    if (s[0].y >= M)
        s[0].y = 0;
    if (s[0].y < 0)
        s[0].y = M - 1;
    if (s[0].x == f.x && s[0].y == f.y) {
        if (num < 100)
            num++;
        f.x = rand() % N;
        f.y = rand() % M;
    }
    for (int i = 1; i < num; i++) {
        if (s[0].x == s[i].x && s[0].y == s[i].y) {
            num = i;
            break;
        }
    }
}

int main()
{
    srand(time(0));
    RenderWindow window(VideoMode(w, h), "Snake");
    for (int i = 0; i < num; i++) {
        s[i].x = 10 - i;
        s[i].y = 10;
    }
    f.x = rand() % N;
    f.y = rand() % M;
    Clock clock;
    float timer = 0;
    float delay = 0.1f;
    while (window.isOpen()) {
        float elapsed = clock.restart().asSeconds();
        timer += elapsed;
        Event event;
        while (window.pollEvent(event)) {
            if (event.type == Event::Closed)
                window.close();
        }
        if (Keyboard::isKeyPressed(Keyboard::Up) && dir != 0)
            dir = 3;
        if (Keyboard::isKeyPressed(Keyboard::Left) && dir != 2)
            dir = 1;
        if (Keyboard::isKeyPressed(Keyboard::Right) && dir != 1)
            dir = 2;
        if (Keyboard::isKeyPressed(Keyboard::Down) && dir != 3)
            dir = 0;
        if (timer > delay) {
            timer = 0;
            Tick();
        }
        window.clear();
        RectangleShape rectangle(Vector2f(size, size));
        for (int i = 0; i < num; i++) {
            rectangle.setPosition(
                s[i].x * size,
                s[i].y * size
            );
            window.draw(rectangle);
        }
        rectangle.setPosition(
            f.x * size,
            f.y * size
        );
        window.draw(rectangle);
        window.display();
    }
    return 0;
}
alfps

It gets a bit messy with 23 000 lines of code in main.

alfps

Do explain what you think you need it for. Some of the functionality is already there in the C++ standard library.

alfps

Without the code (and preferably a description) people can't give you feedback. You can post the code. Or a link to it.

alfps

Re the anonymous unexplained downvote(s), note that an unexplained downvote is dishonest: these are the actions of dishonest persons, maybe young children.

Anyway, trolls, who by definition are idiots. Yet I had not expected downvoting of this, simple facts. These are really fu****g idiots.

Their noise and general sabotage can be safely ignored.

alfps

Worth noting that for the common use of iteration over a curly braces list, like

for( const int x: {1, 1, 2, 3, 5, 8} ) ( ...

… one formally needs to include the <initializer_list> header.

It's a very messy thing where the core language depends on the library.

There are four such core language support headers: C++98 <typeinfo> and <new>; C++11 <initializer_list>; and C++20 <compare>. One might wrap them up in a common include and be done with it, and I tried that once, but somehow no such cleaning effort pans out. It just fizzles out, one stops using it.

alfps

❞ Repository link: In the comments section.

Why? Please just include all relevant info directly in the posting.

alfps

❞ have zero programming knowledge. I’m entering the tech field for the first time and want to start with C++ from scratch

Do yourself a favor and start with a language where you can do interesting things with orders of magnitude less effort. Then when you've learned at least basics like variables, control flow constructs, functions and classes, you can start on C++. Free resources then include e.g. learncpp.com as a basic tutorial and cppreference.com as a reference.

For example, C++ in itself doesn't support graphics or sound or windowing. Other languages' libraries for this may be written in C++. But at the C++ level it's a really huge effort.

One problem with a more beginner-friendly language like Python or Java or JavaScript (the two first are used at MIT as beginner languages for local apps, while JavaScript is more for web apps/pages) is that they're all or most all based on reference semantics. With reference semantics an assignment a = b; makes a refer to the same thing as b. C++ is instead based on value semantics where a = b; makes a an independent copy of b. I couldn't think of any beginner friendly languages with value semantics so I asked the Google AI. It listed Racket, Swift and Elm, but it may be wrong about Swift, and anyway Swift is in practice a Mac only language.

alfps

❞ Is it? If that's the case, why doesn't comparing two Mat3 objects work when the inline definition of the == operator is removed?

Thanks you're right, that's was a frivolous claim I made. I usually advice others to try things out before posting and I did that, but I did not try enough. Note to self: it's also a good idea to think before posting…

/u/no-sig-available was right, it's about the compiler trying out the reverse order of arguments, which it can do for == and <=>, called the "primary" operators. cppreference was not a good help, it's buried this topic somewhere, but I found a blog article about it: (https://brevzin.github.io/c++/2019/07/28/comparisons-cpp20/).

So the compiler's claim of "overloaded functions" plural, was incorrect. But I still do not understand everything about this. It seems sort of needlessly complex with arbitrary special cases. :(


❞ Why friend?

friend is a convenient way to define a non-member function inline in the class.

But why non-member?

Because in the general case non-member supports calls of non-primary operators where the first argument is not an instance of the class:

template< class T > using in_ = const T&;

struct S
{
    S() {}
    S( int ) {}
    friend auto operator<( in_<S>, in_<S> ) -> bool { return false; }
};

struct T
{
    T() {}
    T( int ) {}
    auto operator<( in_<T> ) const -> bool { return false; }
};

auto main() -> int
{
    (void) (7 < S());   // Compiles! Even as C++17. But `7.operator<( S() )` is ungood.
    (void) (7 < T());   // Does not compile, even as C++20.
}
alfps

An ==operator that is const is generated by default because the code doesn't declare one.
Both the code's operator and the generated operator fit the usage in the ASSERT.

EDIT (replacement text): for == and <=> the compiler can try the reverse order of arguments to get a better fit. It did, and the reverse order was an equally non-perfect fit, which it reported misleadingly/incorrectly that there was "overloaded functions" plural.

Technical solution: simply add const to the function, i.e.

    bool operator==(const Mat3& other) const

Real solution is however as you've already done, to make that a friend function with two parameters.

And as others have noted already, you can use = default to let the compiler generate the code for that operator:

namespace Tc
{
    struct Mat3
    {
        float Cells[9] = {};
        friend bool operator==(const Mat3& a, const Mat3& b) = default;
    };
}  // Tc

void Mat3Test()
{
    (void) (Tc::Mat3 { 1.0f, 2.0f } == Tc::Mat3 { 1.0f, 2.0f });
}
alfps

❞ Can you explain the std::filesystem thing?

Well it's complicated because it is about text encodings and portability, but the short of it is that the original Boost Filesystem was intended to support char based file handling code that in particular would also work in Windows, that was the reason for the library, and C++20 botched that original rationale.


Essentially to work around the C++20 sabotage, to reliably create a path from UTF-8 or reliably get an UTF-8 encoded std::string from a path you have to use functions like in the following C++20 code:

#pragma once
#include <filesystem>
#include <string>
#include <string_view>

namespace cppm {
    namespace std_fs = std::filesystem;

    using   std::string,                // <string>
            std::string_view;           // <string_view>

    inline auto std_path_from_spec( in_<string_view> spec )
        -> std_fs::path
    {
        const auto  p_start     = reinterpret_cast<const char8_t*>( spec.data() );
        const auto  p_beyond    = p_start + spec.size();
        return std_fs::path( p_start, p_beyond );
    }

    inline auto string_from_std_path( in_<std_fs::path> path )
        -> string
    {
        const std::u8string result = path.u8string();
        return string( result.begin(), result.end() );
    }
}  // cppm

Note the silly copying in string_from_std_path. No such copying was needed in C++17. C++20 changed the return type of path::u8string() to incompatible char8_t based std::u8string.

Essentially std::filesystem::path is now easy to use incorrectly, and hard to use correctly.


Correct usage is also made hard by a change in Windows mid 2019, when Windows got support for UTF-8.

This introduced a distinction between process Windows ANSI encoding and system Windows ANSI encoding.

The filesystem specification was not updated and it's sufficiently vague that g++ and Visual C++ now use incompatible interpretations. Though it works with both compilers for an all UTF-8 environment, one can get weird results with the system compiler Visual C++ when one hasn't used an application manifest to set the process ANSI code page to UTF-8. E.g. consider

#include <filesystem>
#include <iostream>
#include <print>
namespace fs = std::filesystem;     // <filesystem>
using   std::cout,                  // <iostream>
        std::print;                 // <print>

auto main() -> int
{
    cout << "The C++ basic character set is " << ("\u00E7"[1] == '\xA7'? "" : "NOT ") << "UTF-8.\n";

    const fs::path path_a =  "A.blåbærsyltetøy.recipe";
    const fs::path path_u = u"U.blåbærsyltetøy.recipe";     // Will be interpreted as UTF-8 b/c type.
    for( const fs::path* pp: {&path_a, &path_u} ) {
        cout << "\n";
        cout << "cout result: \"" << pp->string() << "\"\n";
        print(  "print result: \"{}\"\n", pp->string() );
    }
}

Without an application manifest, compiling with Visual C++ yielded

[c:\@\forums\reddit\007 filesystem problems]
> set cl
CL=/nologo /utf-8 /EHsc /GR /permissive- /std:c++latest /Zc:__cplusplus /Zc:preprocessor /W4 /wd4459 /D _CRT_SECURE_NO_WARNINGS=1 /D _STL_SECURE_NO_WARNINGS=1

[c:\@\forums\reddit\007 filesystem problems]
> cl encoding.cpp /Fe"b.exe"
encoding.cpp

[c:\@\forums\reddit\007 filesystem problems]
> chcp & b
Active code page: 65001
The C++ basic character set is UTF-8.

cout result: "A.blåbærsyltetøy.recipe"
print result: "A.blåbærsyltetøy.recipe"

cout result: "U.bl�b�rsyltet�y.recipe"
print result: "U.bl�b�rsyltet�y.recipe"

The apparently correct result for the normal char based path specification is due to a common masking effect: since the literals encoding is UTF-8 path is given a sequence of UTF-8 bytes, which due to the char type it interprets as system Windows ANSI encoded text, which it translates to UTF-16. Then when the program calls .string() that internal UTF-16 text is translated back to system Windows ANSI, which yields the original bytes, which are UTF-8. Output that to a device expecting UTF-8, and voilà, the original source code text appears...

It's a case of two or three wrongs making a right, but as you can see just for that special case.

alfps

I believe the register keyword was not much used. We now leave it to the compiler to do loop unrolling and Duff's device just gathers dust. I just wish they could remove that keyword entirely so it could be used as function name.

Throw-specifications were a drastic removal. Also export templates. And the sabotage of std::filesystem, that was very drastic, even if it had only been in place for years, not decades. And oh, the wide string conversions. So it's not like the committee is afraid of breaking legacy code in general.

They could and should also remove multibyte character literals. But I believe that would be just too practical.

alfps

❞ If you have a const getter for the state you are worried about, you can check whether it's different from input object.

Not really.

A literal as C_string_value constructor argument should store a pointer to that string (plus length), but the same literal repeated in a test afterwards may be a different object, depending on the compiler and options.

The actual C_string_value code currently only detects const char(&)[n] and relies on that as a convention because AFAIK there's no way to test for literal string. But ideally it should copy such an array when it's not a literal, and then a test using a stored string in a test would no longer be possible. Anyway such a test with fake literal would rely on an implementation aspect that could change; I believe it's best to test exactly that which is to be tested rather than something one gathers should give the same effect.


❞ you didn't show what's the object you are testing

I believed that was irrelevant for the question, I'm sorry.

You can check out the code for class at (https://pastebin.com/4H717x6R). It's not a final thing but it works. Originally I had special support for rvalue std::string to make that O(1) but I removed that in the process of making it work.

alfps

❞ Do we write a getter? No. We leverage RAII. Resource Acquisition. Instead of the state machine being an entirely internal, private implementation, we composite the parent class in terms of a state machine.

This is an interesting approach, important to know about. I've probably done this numerous times but I don't know a name for it. Possibly just a refactoring, to "factor out" the aspect that needs to be trusted/tested?

However it's not always applicable, and I believe my example is one case where it isn't.

But it's possible that a fresh pair of eyes can see some way that I didn't, so here are the class requirements (it's all in support of OS specific code, which is why it's about zero terminated C strings):

  • A C_string_value holds a zero terminated string.
  • Logical copying is O(1).
  • Construction from (just) an ordinary string literal is O(1).
  • Construction from (just) a const char* or char* copies the string to a new buffer and can be O(n).
  • Construction from (just) a std::string should also be supported, also allowed to be O(n).
  • .c_str() produces pointer to the string, O(1).
  • .std_sv() produces (exactly, for e.g. the {fmt} library) a std::string_view, O(1).

It's mainly the two first construction requirements that brought about the need for testing internal state, because it's a little tricky to get right.

The presented informal test code shows the desired outcomes:

const auto lit = C_string_value( "Literal" );
const auto dyn = C_string_value( +"Dynamic" );

I think that even if you don't see a way to factor out the construction logic it can be interesting/useful to learn how you would do this. :)

If you want I can share my code, but I think that if I just present it it could influence/restrict your thinking, so I don't just dump it here. TIA.

alfps

❞ So you test the public interface only. If that is insufficient, that tells me how flawed your design is.

Oh, that's absolutely not so.

In the presented example I test whether a costly dynamic copy is used. There are no stringent performance requirements (they would be almost impossible to formalize) but still the reason for existence of such a class rather than just using an always copying std::string, is to avoid dynamic allocation and copying. To not test that most important aspect would be super stupid.

One might argue that efficiency that can't be quantified should be disregarded. So there's no reason to use std::string_view, just use std::string. But I hope that there are far more people than only me who think such arguments show a disconnect from reality and disregard the argument instead.

alfps

❞ It is a mortal sin to have the code know that you are testing, and then behave differently. What is the use of testing code that is different from the one running on the production system?

You're arguing that my method is good, and that conditional code is bad.

However the impression your wording leaves to superficial readers can be the opposite.


❞ It is nobody's business to know how the result is produced, just that it is correct.

Oh, that's absolutely not so.

In the presented example I test whether a costly dynamic copy is used. There are no stringent performance requirements (indeed they would be almost impossible to formalize) but still the reason for existence of such a class rather than just using a copying std::string, is to avoid dynamic allocation. To not test that most important aspect would be super stupid.

alfps

Uhm, thanks, you're the second who recommends conditional code. But I avoid conditional code on principle; to me it's so a big a no no that I didn't even include it in my list of 4 alternatives. That avoidance is about complexity, and that conditional code can change the binary layout, introduce name collisions, whatnot.

The nice thing I see about the member pointer trick, for larger scale development (rather than what I currently do), is that access functions like the shown kind_of can be automatically generated in the testing/debugging code without affecting the original class.

E.g. code generation via the preprocessor (in order to be able to handle the function name) or a build time script, whatever.


❞ This seems like a bad test

Not sure if you're referring to your code example, or mine.

The test I showed is a very good one, and doesn't access a variable: it accesses a function, that's called. It's good because a crucial implementation aspect is how the data is stored, avoiding needless dynamically allocated copies for literal strings. C++'s overload resolution makes that difficult to get right so it better be tested, but client code does only depend (if it does) on the knowledge that literals are stored and copied superfast.

It could be tested by measuring of course. But that would be a silly needlessly inefficient test. Like listening to an engine for some time instead of checking directly the possibly troublesome internal aspect.

alfps

You need different names for different variables in the same curly braces block.

Some times it can be a good idea to input a whole line to a std::string, and then use std::getline for that. Then convert to integer, or see if that fails, via e.g. std::stoi. But you don't need that here.

Just input directly to int variable, like this:

#include <iostream>

int main()
{
    std::cout << "dividendo? ";
    int Dividendo;  std::cin >> Dividendo;

    std::cout << "divisor? ";
    int Divisor;  std::cin >> Divisor;

    const int Cociente = Dividendo/Divisor;
    const int Resto = Dividendo%Divisor;

    std::cout << Cociente << " + " << Resto << "/" << Divisor << std::endl;
}

As you advance you will add failure checking to such code.

For novice work a reasonable response to failure, such as the user typing garamond instead of 23, is to just terminate via std::abort.