Skip to main content
Bounty Awarded with 50 reputation awarded by CommunityBot
added 146 characters in body
Source Link
user673679
  • 12.2k
  • 2
  • 34
  • 65
  • Same issues as Printer.

  • It would be reasonable to merge the extract_id and is_expected_id into one function. This means we don't return state that may or may not be valid and then have to pass it into a separate function to check. Sticking with the input operator conventions, we get: std::istream& extract_id(std::istream& is, std::string& id, std::string const& expected_id);. We can use the state of the stream to indicate success / failure, and don't need the extra boolean. e.g.:

     std::string id;
     if (!extract_id(is, id, last_id)) // checks stream fail / bad bits (eof will be caught at next read)
         return is;
    
  • One test-case with valid input calling the high-level stream operator is not enough to properly confirm the behavior. We need to check the behavior of the individual functions and think about edge cases. For example, for the extract_id function, one might expect the following:

     TEST(Test_Reader, IStreamWithValidIDAndDot) { ... }
     TEST(Test_Reader, IStreamWithOnlyDot) { ... }
     TEST(Test_Reader, IStreamWithIDAndNoDot) { ... }
     TEST(Test_Reader, EmptyIStream) { ... }
     TEST(Test_Reader, IStreamWithValidIDAndDotAndBadbitSet) { ... }
     TEST(Test_Reader, IStreamWithValidIDAndDotAndFailitSet) { ... }
     TEST(Test_Reader, IStreamWithValidIDAndDotAndEofbitSet) { ... }
    
  • discard, not dischard.

  • Extra parentheses in FakeType::insert() and EnableValve::insert():

     void insert(std::string(s)) {
         m = s;
     }
    

    It's fine to take the argument by value, but we can then move it into place:

     void insert(std::string s) {
         m = std::move(s);
     }
    
  • We have an operator>> for the FakeTypes, but we aren't using it?

  • I think we only need to set last_id once for a given datablock.

  • Same issues as Printer.

  • It would be reasonable to merge the extract_id and is_expected_id into one function. This means we don't return state that may or may not be valid and then have to pass it into a separate function to check. Sticking with the input operator conventions, we get: std::istream& extract_id(std::istream& is, std::string& id, std::string const& expected_id);. We can use the state of the stream to indicate success / failure, and don't need the extra boolean.

  • One test-case with valid input calling the high-level stream operator is not enough to properly confirm the behavior. We need to check the behavior of the individual functions and think about edge cases. For example, for the extract_id function, one might expect the following:

     TEST(Test_Reader, IStreamWithValidIDAndDot) { ... }
     TEST(Test_Reader, IStreamWithOnlyDot) { ... }
     TEST(Test_Reader, IStreamWithIDAndNoDot) { ... }
     TEST(Test_Reader, EmptyIStream) { ... }
     TEST(Test_Reader, IStreamWithValidIDAndDotAndBadbitSet) { ... }
     TEST(Test_Reader, IStreamWithValidIDAndDotAndFailitSet) { ... }
     TEST(Test_Reader, IStreamWithValidIDAndDotAndEofbitSet) { ... }
    
  • discard, not dischard.

  • Extra parentheses in FakeType::insert() and EnableValve::insert():

     void insert(std::string(s)) {
         m = s;
     }
    

    It's fine to take the argument by value, but we can then move it into place:

     void insert(std::string s) {
         m = std::move(s);
     }
    
  • We have an operator>> for the FakeTypes, but we aren't using it?

  • I think we only need to set last_id once for a given datablock.

  • Same issues as Printer.

  • It would be reasonable to merge the extract_id and is_expected_id into one function. This means we don't return state that may or may not be valid and then have to pass it into a separate function to check. Sticking with the input operator conventions, we get: std::istream& extract_id(std::istream& is, std::string& id, std::string const& expected_id);. We can use the state of the stream to indicate success / failure, and don't need the extra boolean. e.g.:

     std::string id;
     if (!extract_id(is, id, last_id)) // checks stream fail / bad bits (eof will be caught at next read)
         return is;
    
  • One test-case with valid input calling the high-level stream operator is not enough to properly confirm the behavior. We need to check the behavior of the individual functions and think about edge cases. For example, for the extract_id function, one might expect the following:

     TEST(Test_Reader, IStreamWithValidIDAndDot) { ... }
     TEST(Test_Reader, IStreamWithOnlyDot) { ... }
     TEST(Test_Reader, IStreamWithIDAndNoDot) { ... }
     TEST(Test_Reader, EmptyIStream) { ... }
     TEST(Test_Reader, IStreamWithValidIDAndDotAndBadbitSet) { ... }
     TEST(Test_Reader, IStreamWithValidIDAndDotAndFailitSet) { ... }
     TEST(Test_Reader, IStreamWithValidIDAndDotAndEofbitSet) { ... }
    
  • discard, not dischard.

  • Extra parentheses in FakeType::insert() and EnableValve::insert():

     void insert(std::string(s)) {
         m = s;
     }
    

    It's fine to take the argument by value, but we can then move it into place:

     void insert(std::string s) {
         m = std::move(s);
     }
    
  • We have an operator>> for the FakeTypes, but we aren't using it?

  • I think we only need to set last_id once for a given datablock.

added 19 characters in body
Source Link
user673679
  • 12.2k
  • 2
  • 34
  • 65
  • Should an empty parameter pack be allowed? It looks like this would break the print / read functions, so we should probably check for that too.

  • What's the purpose of the protected member functions? Do we actually need inheritance or could we make everything public except m_id? That would make the class a lot easier to test. Access control in C++ is best used to prevent breaking class-invariants and hide complex internal functionality. The only invariant here seems to be that the ID shouldn't be changed after creation.

  • The constructor does some unnecessary copyingdoesn't need to create a temporary tuple. Use perfect forwarding insteadWe can move the arguments directly into m_data:

     explicit Variadic_datablock(std::string id, T&&T... args):
         m_id{ std::move(id) },
         m_data{ std::forward<T>move(args)... }
     {
    
     }
    
  • Should an empty parameter pack be allowed? It looks like this would break the print / read functions, so we should probably check for that too.

  • What's the purpose of the protected member functions? Do we actually need inheritance or could we make everything public except m_id? That would make the class a lot easier to test. Access control in C++ is best used to prevent breaking class-invariants and hide complex internal functionality. The only invariant here seems to be that the ID shouldn't be changed after creation.

  • The constructor does some unnecessary copying. Use perfect forwarding instead:

     explicit Variadic_datablock(std::string id, T&&... args):
         m_id{ std::move(id) },
         m_data{ std::forward<T>(args)... }
     {
    
     }
    
  • Should an empty parameter pack be allowed? It looks like this would break the print / read functions, so we should probably check for that too.

  • What's the purpose of the protected member functions? Do we actually need inheritance or could we make everything public except m_id? That would make the class a lot easier to test. Access control in C++ is best used to prevent breaking class-invariants and hide complex internal functionality. The only invariant here seems to be that the ID shouldn't be changed after creation.

  • The constructor doesn't need to create a temporary tuple. We can move the arguments directly into m_data:

     explicit Variadic_datablock(std::string id, T... args):
         m_id{ std::move(id) },
         m_data{ std::move(args)... }
     {
    
     }
    
Source Link
user673679
  • 12.2k
  • 2
  • 34
  • 65

Variadic_datablock:

* > class Variadic_datablock > /* Requires elements in T... are unique (not repeat of types) */

We could enforce this requirement with std::enable_if or a static_assert in the class, combined with some template-metaprogramming.

  • Should an empty parameter pack be allowed? It looks like this would break the print / read functions, so we should probably check for that too.

  • What's the purpose of the protected member functions? Do we actually need inheritance or could we make everything public except m_id? That would make the class a lot easier to test. Access control in C++ is best used to prevent breaking class-invariants and hide complex internal functionality. The only invariant here seems to be that the ID shouldn't be changed after creation.

  • The constructor does some unnecessary copying. Use perfect forwarding instead:

     explicit Variadic_datablock(std::string id, T&&... args):
         m_id{ std::move(id) },
         m_data{ std::forward<T>(args)... }
     {
    
     }
    

Printer:

  • There's some unnecessary duplication. We could definitely abstract this bit into a print_element(os, std::get<n - 1>(t));

     auto type_name =
         extract_type_name(typeid(std::get<n - 1>(t)).name());
    
     os << "   " << id << "." << type_name << " := "
         << std::get<n - 1>(t) << "; " << '\n';
     return os;
    
  • Since the Printer, Reader classes and the print and read functions aren't supposed to be used directly by the user, they could be placed in a detail (or similarly named) namespace.

  • typeid(x).name() is implementation defined. Several different types may have the same name, and the name can even change between invocations of the same program. In other words, it's not something we should use for serialization. I'd suggest adding a static const std::string data-member to each element class.


Reader:

  • Same issues as Printer.

  • It would be reasonable to merge the extract_id and is_expected_id into one function. This means we don't return state that may or may not be valid and then have to pass it into a separate function to check. Sticking with the input operator conventions, we get: std::istream& extract_id(std::istream& is, std::string& id, std::string const& expected_id);. We can use the state of the stream to indicate success / failure, and don't need the extra boolean.

  • One test-case with valid input calling the high-level stream operator is not enough to properly confirm the behavior. We need to check the behavior of the individual functions and think about edge cases. For example, for the extract_id function, one might expect the following:

     TEST(Test_Reader, IStreamWithValidIDAndDot) { ... }
     TEST(Test_Reader, IStreamWithOnlyDot) { ... }
     TEST(Test_Reader, IStreamWithIDAndNoDot) { ... }
     TEST(Test_Reader, EmptyIStream) { ... }
     TEST(Test_Reader, IStreamWithValidIDAndDotAndBadbitSet) { ... }
     TEST(Test_Reader, IStreamWithValidIDAndDotAndFailitSet) { ... }
     TEST(Test_Reader, IStreamWithValidIDAndDotAndEofbitSet) { ... }
    
  • discard, not dischard.

  • Extra parentheses in FakeType::insert() and EnableValve::insert():

     void insert(std::string(s)) {
         m = s;
     }
    

    It's fine to take the argument by value, but we can then move it into place:

     void insert(std::string s) {
         m = std::move(s);
     }
    
  • We have an operator>> for the FakeTypes, but we aren't using it?

  • I think we only need to set last_id once for a given datablock.