AskOverflow.Dev

AskOverflow.Dev Logo AskOverflow.Dev Logo

AskOverflow.Dev Navigation

  • 主页
  • 系统&网络
  • Ubuntu
  • Unix
  • DBA
  • Computer
  • Coding
  • LangChain

Mobile menu

Close
  • 主页
  • 系统&网络
    • 最新
    • 热门
    • 标签
  • Ubuntu
    • 最新
    • 热门
    • 标签
  • Unix
    • 最新
    • 标签
  • DBA
    • 最新
    • 标签
  • Computer
    • 最新
    • 标签
  • Coding
    • 最新
    • 标签
主页 / user-5825294

Enlico's questions

Martin Hope
Enlico
Asked: 2025-04-25 05:49:56 +0800 CST

在 STM 的背景下,事务日志在概念上是什么样的,以及当事务在几次重试后成功时它会如何演变?

  • 6

例如,考虑这个函数,它可以在 WM 中使用,允许在给定的显示器上将窗口从一个桌面移动到另一个桌面,

moveWindowSTM :: Display -> Window -> Desktop -> Desktop -> STM ()
moveWindowSTM disp win a b = do
  wa <- readTVar ma
  wb <- readTVar mb
  writeTVar ma (Set.delete win wa)
  writeTVar mb (Set.insert win wb)
 where
  ma = disp ! a
  mb = disp ! b

显然还有它的IO包装,

moveWindow :: Display -> Window -> Desktop -> Desktop -> IO ()
moveWindow disp win a b = atomically $ moveWindowSTM disp win a b

然后假设

  • 我们的交易在第三次尝试时成功了,
  • ma因为在第一次尝试时,它会因另一个并发事务提交而失效,该事务在我们的事务之后立即更改了内部的值wa <- readTVar ma,
  • 而在第二次尝试时,它被另一个并发事务无效,该事务开始提交并更改了我们的事务中的一个或ma两个的内容。mbwriteTVar ma (Set.delete win wa)

在这种情况下,交易日志将如何演变,以及验证在什么时候会失败?

Simon Marlow 所著《Haskell 中的并行和并发编程》的相关摘录如下(但 Simon Peyton Jones 所著的论文《美丽的并发性》中也提供了类似的信息):

STM 事务的工作原理是累积事务期间迄今为止发生的操作的日志。日志有三种readTVar用途:writeTVar

  • 通过将writeTVar操作存储在日志中而不是立即将其应用到主内存中,丢弃事务的影响很容易;我们只需丢弃日志即可。因此,中止事务的代价很小且固定。

  • 每个人都readTVar必须遍历日志来检查 是否是TVar由之前的 写入的writeTVar。因此,readTVar是日志长度上的 O(n) 操作。

  • 因为日志包含所有操作的记录readTVar,所以可以使用它来发现事务期间的完整TVars读取集,我们需要知道这些才能实现retry。

当事务结束时,STM 实现会将日志与内存内容进行比较。如果当前内存内容与 读取的值匹配readTVar,则将事务的结果提交到内存;如果不匹配,则丢弃日志并从头开始重新运行事务。此过程以原子方式进行,方法是在TVars事务执行期间锁定所有涉及的 。GHC 中的 STM 实现不使用全局锁;TVars提交期间仅锁定涉及事务的 ,因此对不相交的 集合进行操作的事务TVars可以不受干扰地继续进行。

haskell
  • 2 个回答
  • 83 Views
Martin Hope
Enlico
Asked: 2025-04-24 14:30:40 +0800 CST

为什么对依赖名称进行非限定名称查找时找不到在模板声明和实例化之间声明的 lambda?

  • 10

以下程序不正确,

inline constexpr auto makeFoo = [](auto const& x) {
    return getFoo(x);
};

inline constexpr auto getFoo = [](int const&) {
    return 1;
};

int bar() {
    return makeFoo(32);
}

并反转和的定义使其正确makeFoogetFoo。

我理解,基于对两阶段名称查找的“非正式”理解:

  • makeFoo是一个通用的 lambda,并且getFoo在其主体中对类型尚不清楚的参数进行调用,因此getFoo还不能查找名称;
  • 当看到调用时makeFoo(32),lambdaoperator()会被实例化auto = int,因此会发生对非限定名称的第二阶段查找getFoo;
  • 然而,这只是 ADL,所以它找不到getFoo,因为那是 lambda 的名称,即对象的名称,而不是函数的名称。

有人可以指导我找到“反映”上述解释的标准草案部分吗?

我认为[basic.lookup.unqual]和[temp.dep.res]可能包含解释该案例所需的所有内容,但我无法将这些部分拼凑在一起。

c++
  • 2 个回答
  • 179 Views
Martin Hope
Enlico
Asked: 2025-04-21 21:30:49 +0800 CST

如果线程在未持有 MVar 的情况下被取消调度,GHC 的公平性保证怎么会不显现出来呢?

  • 7

在 Simon Marlow 所著的《Haskell 中的并行和并发编程》中,第 7 章从第 125 页开始,其中有这样一个例子:

import Control.Concurrent
import Control.Monad
import System.IO

main :: IO ()
main = do
  hSetBuffering stdout NoBuffering
  _ <- forkIO (replicateM_ 100000 (putChar 'A'))
  replicateM_ 100000 (putChar 'B')

其输出如下

BBBBBBBABABABAAAAAAA

100000(顺便说一下,这是我在上面的代码片段中更改10并运行程序时真正得到的输出)。

在本章末尾,第 140 页,有如下评论(我强调):

[…] 这是公平性保证在实践中的一个例子。[…]。因此,这会导致两个线程之间完美地交替。交替模式被打破的唯一情况是,当一个线程没有持有 时,它被取消调度MVar。事实上,由于抢占,这种情况确实会不时发生,我们偶尔会在输出中看到一个由单个字母组成的长字符串。

MVar根据我给出的输出,是的,我可以看到交替被破坏了,但我不太理解上面的解释。第一个例子中甚至没有。

haskell
  • 1 个回答
  • 54 Views
Martin Hope
Enlico
Asked: 2025-04-21 21:12:35 +0800 CST

澄清在并发环境下 MVar 内部状态不变性的重要性

  • 6

在Simon Marlow 所著的《Haskell 中的并行和并发编程》第 133 和 134 页中显示了以下代码:

type Name        = String
type PhoneNumber = String
type PhoneBook   = Map Name PhoneNumber

newtype PhoneBookState = PhoneBookState (MVar PhoneBook)

lookup :: PhoneBookState -> Name -> IO (Maybe PhoneNumber)
lookup (PhoneBookState m) name = do
  book <- takeMVar m
  putMVar m book
  return (Map.lookup name book)

下一页有一些观察。不过,第二段的最后部分让我有点困惑。以下是该段的前半部分:

在可变包装器中使用不可变数据结构还有其他好处。需要注意的是,在lookup操作中,我们只是获取了状态的当前值,然后复杂的操作在/序列Map.lookup之外进行。这对于并发性来说非常有利,因为这意味着锁只会被持有很短的时间。takeMVarputMVar

这对我来说相当清楚,因为我理解 会takeMVar锁定,然后putMVar立即释放,这样后续对 的并发访问PhoneBookState就不会再受到任何阻碍。同时,我还发现 可能会发生进一步的变异,所以当 以 为返回值MVar时,该对可能已经从映射中移除(或者,如果返回,则可能已经将一些对添加到映射中)。lookupJust smth(name, smth)Nothing(name, smth)

然而,我不明白该段接下来的部分想要表达什么:

这之所以可能,是因为状态的值是不可变的。如果数据结构是可变的,那么我们在操作它时就必须持有锁。⁵


⁵. 另一种选择是使用无锁算法,但这非常复杂,很难做到正确。

这是什么意思?作者指的是将某些 API 暴露给 mutated 的情况Map吗?除非我们在 中,否则这根本不可能,IO对吧?或者他指的是对 的内容执行的修改(由释放锁m后的其他线程执行)会以某种方式影响 的内容?putMVarbook

haskell
  • 2 个回答
  • 58 Views
Martin Hope
Enlico
Asked: 2025-04-19 19:25:32 +0800 CST

为什么括号的第一个参数最多执行一次阻塞操作?

  • 7

在 Simon Marlow 所著的《Haskell 中的并行和并发编程》中,展示了以下实现bracket:

bracket
        :: IO a         -- ^ computation to run first (\"acquire resource\")
        -> (a -> IO b)  -- ^ computation to run last (\"release resource\")
        -> (a -> IO c)  -- ^ computation to run in-between
        -> IO c         -- returns the value from the in-between computation
bracket before after thing =
  mask $ \restore -> do
    a <- before
    r <- restore (thing a) `onException` after a
    _ <- after a
    return r

并附有评论。以下是让我困惑的部分

[…] 包含阻塞操作是正常的;如果在阻塞before期间引发异常,则不会造成任何损害。但应该只执行一个阻塞操作。第二个阻塞操作引发的异常不会导致执行。[…]beforebeforeafter

我不太理解该评论,所以我想对其进行一些澄清。

要清楚的是,我甚至不明白“没有造成任何伤害”这一部分:在前面几页(第 159 页)中,以下调用的失败(第二次)尝试takeMVar,根据其内容执行操作,最后将该操作的结果放回MVarvia中,putMVar如下所示,

problem :: MVar a -> (a -> IO a) -> IO ()
problem m f = mask $ \restore -> do
  a <- takeMVar m
  r <- restore (f a) `catch`  \e -> do putMVar m a; throw e
  putMVar m r

在看这个例子时,我“接受”一个事实,直到它返回时takeMVar才会被篡改m;事实上,这是从文本中摘取的:

直到返回时为止引发异常都是安全的takeMVar。

我觉得我明白接下来的内容(我认为)。

但回到 的实现bracket,如果before内部使用takeMVar,并且在 之后发生异步异常takeMVar(从而清空MVar)会怎么样?这是否没有问题,因为我们使用了mask+ restore,也就是说,这样的异常会被延迟到 的参数(restore即 )thing a开始执行,此时必要的异常处理程序已经到位,在本例中是通过 `onException` after a?

before那么,如果再次调用阻塞操作,为了简单起见,再说一遍,会发生什么问题呢takeMVar?问题是否是因为异常可能在阻塞时 takeMVar发生,因此在异常未被屏蔽的时间窗口内,异常会冒泡出来bracket,导致MVar第一个参数takeMVar不为空,但处于非原始状态,即 givenafter还没有机会运行?

是这个吗?

此外,文档页面没有提及这一点,或者我不明白它是如何暗示的。

exception
  • 1 个回答
  • 91 Views
Martin Hope
Enlico
Asked: 2025-02-08 18:13:37 +0800 CST

这两个函数的行为是否有任何不同,从而导致 GHC 将它们编译为不同的目标代码?

  • 5

这是一个模块:

module Foo where

foo ws = let ws' = take 3 $ ws ++ repeat mempty
             x = ws' !! 0
             y = ws' !! 1
             z = ws' !! 2
         in [x, y, z]

这是另一个

module Foo where

foo ws = let (x:y:z:_) = take 3 $ ws ++ repeat mempty
         in [x, y, z]

通过ghc -O2 -c foo.hs,它们分别编译为 8072 和 5288 字节。不确定哪个最好,也不知道如何测试它们,但我猜它们在性能方面不可能表现完全相同,因为它们是不同的。

这两个函数的执行结果有什么不同吗?如果没有,那么生成的二进制文件中的差异是否是由于优化失误造成的?还是其他原因?

haskell
  • 1 个回答
  • 57 Views
Martin Hope
Enlico
Asked: 2024-12-28 14:03:08 +0800 CST

在生产代码中发现了伪造的 IO 类型类的实例,但在测试中却没有发现

  • 7

我有很多IO基于的操作,其中最简单的一个如下:

-- Loop.hs
module Loop where
import System.Console.ANSI (setCursorPosition)
type Pos = (Int, Int)
setCursorPosition' :: Pos -> IO ()
setCursorPosition' = uncurry setCursorPosition

此时,从上面的开始,我决定根据实现的类型约束来编写这些函数,而不是像这个答案所建议的那样IO硬编码。IO

所以我做的是

  • 定义一个FakeIO类型class及其简单实现IO:
    -- Interfaces.hs
    module Interfaces where
    import qualified System.Console.ANSI as ANSI (setCursorPosition)
    
    class FakeIO m where
      setCursorPosition :: Int -> Int -> m ()
    
    instance FakeIO IO where
      setCursorPosition = ANSI.setCursorPosition
    
  • 更改setCursorPosition'为使用这个接口:
    -- Loop.hs
    module Loop where
    import Interfaces
    type Pos = (Int, Int)
    setCursorPosition' :: FakeIO m => Pos -> m ()
    setCursorPosition' = uncurry setCursorPosition
    

这使得程序仍然可以正常工作(通过cabal run),证明“重构”是正确的。

但当我尝试利用此重构进行测试时,我遇到了困难。我所做的就是编写以下测试:

-- test/Main.hs
module Main where

import Control.Monad (unless)
import System.Exit (exitFailure)
import MTLPrelude (State, execState, modify')
import Test.QuickCheck

import Loop
import Interfaces

data MockTerminal = MockTerminal {
  pos :: (Int, Int)
} deriving Eq

instance FakeIO (State MockTerminal) where
  setCursorPosition y x = modify' $ \m -> MockTerminal { pos = (y, x) }

main :: IO ()
main = do
  result <- quickCheckResult tCenter
  unless (isSuccess result) exitFailure

tCenter :: Bool
tCenter = (setCursorPosition' (1,1))
          `execState` MockTerminal { pos = (0,0)}
          == MockTerminal { pos = (1,1) }

编译失败(通过cabal test),因为

error: [GHC-39999]
    • No instance for ‘snakegame-0.1.0.0:Loop:Interfaces.FakeIO
                         (StateT MockTerminal Identity)’
        arising from a use of ‘setCursorPosition'’
    • In the first argument of ‘execState’, namely
        ‘(setCursorPosition' (1, 1))’
      In the first argument of ‘(==)’, namely
        ‘(setCursorPosition' (1, 1))
           `execState` MockTerminal {pos = (0, 0)}’
      In the expression:
        (setCursorPosition' (1, 1)) `execState` MockTerminal {pos = (0, 0)}
          == MockTerminal {pos = (1, 1)}
   |
41 | tCenter = (setCursorPosition' (1,1))
   |            ^^^^^^^^^^^^^^^^^^

我不明白,因为instance FakeIO (State MockTerminal)应该正是snakegame-0.1.0.0:Loop:Interfaces.FakeIO (StateT MockTerminal Identity)编译器声称不存在的实例。

此外,如果我将测试改为使用setCursorPosition 1 1而不是setCursorPosition' (1,1),它会编译并通过,表明instance确实在发挥作用。

instance因此,这与 的定义相结合时一定出了问题setCursorPosition'。


我把示例缩减为以下 4 个文件:

$ tree !(dist-newstyle)
cabal.project  [error opening dir]
LICENSE  [error opening dir]
Session.vim  [error opening dir]
snakegame.cabal  [error opening dir]
src
├── Interfaces.hs
├── Loop.hs
└── Main.hs
test
└── Main.hs

2 directories, 8 files

其中:

-- src/Main.hs
module Main where

import Loop

main :: IO ()
main = setCursorPosition' (1,1)
-- src/Loop.hs
module Loop (setCursorPosition') where

import Interfaces
type Pos = (Int, Int)

setCursorPosition' :: FakeIO m => Pos -> m ()
setCursorPosition' = uncurry setCursorPosition
-- test/Main.hs
module Main where

import Control.Monad (unless)
import System.Exit (exitFailure)
import MTLPrelude (State, execState, modify')
import Test.QuickCheck

import Loop
import Interfaces

data MockTerminal = MockTerminal {
  pos :: (Int, Int)
} deriving Eq

instance FakeIO (State MockTerminal) where
  setCursorPosition y x = modify' $ \m -> MockTerminal { pos = (y, x) }
  putChar _ = modify' id

main :: IO ()
main = do
  result <- quickCheckResult tCenter
  unless (isSuccess result) exitFailure

tCenter :: Bool
tCenter = (setCursorPosition' (1,1))
          `execState` MockTerminal { pos = (0,0)}
          == MockTerminal { pos = (1,1)}
cabal-version: 3.0
name: snakegame
version: 0.1.0.0

common common
    default-language: GHC2024
    build-depends: base >= 4.19.1.0
                 , ansi-terminal
                 , mtl-prelude

common warnings
    ghc-options: -Wall

executable snakegame
    import: warnings, common
    main-is: Main.hs
    other-modules: Loop
                 , Interfaces
    hs-source-dirs: src

library Loop
    import: warnings, common
    exposed-modules: Loop
    hs-source-dirs: src

library Interfaces
    import: warnings, common
    exposed-modules: Interfaces
    hs-source-dirs: src

test-suite Test
    import: warnings, common
    type: exitcode-stdio-1.0
    main-is: Main.hs
    build-depends: QuickCheck
                 , Interfaces
                 , Loop
    hs-source-dirs: test
packages: .

with-compiler: ghc-9.10.1
unit-testing
  • 1 个回答
  • 54 Views
Martin Hope
Enlico
Asked: 2024-12-27 14:16:31 +0800 CST

运行 monad 变换器堆栈所生成的任何东西都不是 == 任何东西?

  • 7

复制:

cabal repl --build-depends=mtl-prelude,transformers
λ> import Data.Mayb
λ> import Control.Monad
λ> import Control.Monad.Trans.Identity
λ> import MTLPrelude
λ> :{
ghci| maybeQuit :: MonadPlus m => Maybe Char -> MaybeT m (Maybe Char)
ghci| maybeQuit key = do
ghci|   case key of
ghci|     Just 'q' -> mzero
ghci|     Just '\ESC' -> mzero
ghci|     _ -> return key
ghci| :}
λ> runIdentityT $ runMaybeT $ maybeQuit (Just 'c')
Just (Just 'c')
λ> runIdentityT $ runMaybeT $ maybeQuit (Just 'q')
Nothing

到目前为止,一切都很好。

但随后:

λ> (runIdentityT $ runMaybeT $ maybeQuit (Just 'q')) == Nothing 
False
λ> isNothing (runIdentityT $ runMaybeT $ maybeQuit (Just 'q'))
False

什么?!


我确实看到这个表达有点多态,

λ> :t runIdentityT $ runMaybeT $ maybeQuit (Just 'q')
runIdentityT $ runMaybeT $ maybeQuit (Just 'q')
  :: MonadPlus f => f (Maybe (Maybe Char))

但我完全不确定这怎么能暗示印刷的内容Nothing可能是除之外的其他内容(==) Nothing。


就上下文而言,我maybeQuit在一个单子堆栈(其中有MaybeT和其他变压器)中运行IO,底部有一个,它按预期工作,但它的实现,因此类型,不需要的功能IO,所以我试图在除之外的单子中对其进行测试IO,这就是我提出这个问题的原因。

现在回想起来,我确实开始看到一些我不太理解的东西:我看到的:t runMaybeT $ maybeQuit (Just 'q')是MonadPlus m => m (Maybe (Maybe Char)),这正是我所期望的,但是然后将其应用于runIdentityT它给出了类型MonadPlus f => f (Maybe (Maybe Char),这是同样的东西,所以我一定是误解了的含义/目的IdentityT,并不禁想到这就是我没有得到我认为应该在原始例子中得到的东西的全部原因。

鉴于上述原因,我尝试通过应用来强制m === []表达,结果更符合我的预期:runMaybeT $ maybeQuit (Just 'c')head

λ> isNothing (head $ runMaybeT $ maybeQuit (Just 'q'))
True
λ> isNothing (head $ runMaybeT $ maybeQuit (Just 'c'))
False

但我还是不明白使用有什么问题runIdentityT。

haskell
  • 1 个回答
  • 45 Views
Martin Hope
Enlico
Asked: 2024-12-16 23:33:09 +0800 CST

(如何)我才能获取当前在终端上给定位置显示的字符?

  • 7

(与上一个问题相关。)

如果我可以通过在终端上Prelude.putChar我已决定的位置打印一个字符System.Console.ANSI.setCursorPosition,我怎样才能获取该位置的字符?

用例是我想要做这样的事情

do
  setCursorPosition a b
  c <- getTheCharacterHere
  putChar $ if c == 'x' then 'y' else 'z'

其中getTheCharacterHere是检索当前显示在通过 设置的位置处的字符的所需操作setCursorPosition。

haskell
  • 1 个回答
  • 42 Views
Martin Hope
Enlico
Asked: 2024-11-30 00:46:21 +0800 CST

通过 const_cast 获取的非 const 引用在 const 对象上调用非 const mem. fun. 是否仅当 fun 实际修改了对象时才会调用 UB?[重复]

  • 5
这个问题已经有答案了:
只要没有实际修改,是否允许在 const 定义的对象上抛弃 const? (1 个答案)
21 小时前关闭。

简而言之,我想问的是,以下代码是否仅当/* body */确实改变了 的值时才调用 UB i,或者如果没有改变,是否也通过调用对象const maybeChange上的非成员函数来调用constUB。

// header.hpp
struct Foo {
  int i;
  void maybeChange();
};

void work(Foo const& foo);
// foo.cpp
#include "header.hpp"
void Foo::maybeChange() {
  /* body */
}
// work.cpp
#include "header.hpp"
void work(Foo const& foo) {
  const_cast<Foo&>(foo).maybeChange();
}
// main.cpp
#include "header.hpp"
Foo const foo{6};

int main() {
  work(foo);
}

我确实看到,如果修改真的发生,问题就会存在,因为这违反了编译器可以做出的合法假设,即foo全局对象不会改变。

但另一方面,http://eel.is/c++draft/dcl.type.cv#4没有展示在被删除的对象const上调用非成员函数的示例,但这实际上并没有修改它,就像我上面的例子一样。它展示了一些简单的例子,比如constconstconst_cast

const int* ciq = new const int (3);     // initialized as required
int* iq = const_cast<int*>(ciq);        // cast required
*iq = 4;                                // undefined behavior: modifies a const object

最后一行确实修改了对象*ciq。

c++
  • 1 个回答
  • 69 Views
Martin Hope
Enlico
Asked: 2024-11-18 18:30:55 +0800 CST

如何在编译时检查是否存在接受给定参数类型的全局范围函数?

  • 13

我觉得我需要什么

如何定义类型特征来检查某个类型的T函数是否::foo(T)已声明?

我发现很难::foo以 SFINAE 友好的方式实现。例如,如果编译器已经到了定义以下内容的地步,

template<typename T>
void f(T t) { foo(t); }

foo如果到现在还没有发现任何东西,那就太好了。

但是一旦我更改foo为::foo,就会出现硬错误。

用例(以防您认为我不需要上述内容)

我有一个这样的定制点:

// this is in Foo.hpp
namespace foos {
inline constexpr struct Foo {
    template <typename T, std::enable_if_t<AdlFooable<std::decay_t<T>>::value, int> = 0>
    constexpr bool operator()(T const& x) const {
        return foo(x);
    }
} foo{};
}

通过为所需类型foos::foo定义 ADL重载,可以定制调用的行为。foo

该特征的定义很简单:

// this is in Foo.hpp
template <typename T, typename = void>
struct AdlFooable : std::false_type {};

template <typename T>
struct AdlFooable<T, std::void_t<decltype(foo(std::declval<T const&>()))>>
    : std::true_type {};

鉴于

// this is in Bar.hpp
namespace bar {
    struct Bar {};
    bool foo(bar::Bar);
}

然后调用

// other includes
#include "Foo.hpp"
#include "Bar.hpp"
bool b = foos::foo(bar::Bar{});

工作正常,将foos::foo调用路由到bar::foo(bar::Bar)通过 ADL 找到的 。无论两个#includes 的顺序如何,这都能很好地工作,这很好,因为如果传递地包含它们,它们可以按任意顺序包含// other includes。

到目前为止,一切都很好。

我不太喜欢这种方法,因为人们可能会错误地定义下面的代码,而不是上面的第二个代码片段,

// this is in Bar.hpp
namespace bar {
    struct Bar {};
}
bool foo(bar::Bar);

在全球范围内foo。

在这种情况下,如果#include "Bar.hpp"恰好在之前出现#include "Foo.hpp",则代码程序将会起作用,因为的主体Foo::operator()将::foo(bar::Bar)通过普通查找进行选择。

但是一旦#includes 的顺序被颠倒过来,代码就会被破坏。

是的,这个错误是foo(bar::Bar)在全局命名空间中进行定义的,但我认为这也是一个纯粹偶然不会被注意到的错误。

这就是为什么我想改变类型特征来表达“发现不合格的调用,但不是通过普通查找”foo(T),或者更直接地说,“foo(std::declval<T>())必须编译,但不能::foo(std::declval<T>())编译”。

c++
  • 1 个回答
  • 235 Views
Martin Hope
Enlico
Asked: 2024-11-16 20:53:56 +0800 CST

std::ranges::enumerate 可以枚举任何可能有效的 C++ 数组吗?

  • 4

有一次我问是否在 GCC 上std::ranges::views::enumerate使用错误的类型(long)进行索引,但显然情况并非如此,因为

std::views::enumerate被指定用作range_difference_t<Base>其索引值。

然而,从草案来看

该类型size_t是实现定义的无符号整数类型,其足够大,可以包含任何对象的字节大小([expr.sizeof])。

因此,我们可以认为一个数组太长,以至于从某个索引开始的尾随元素不符合用于enumerate索引的类型,尽管仍然符合std::size_t定义,但在给定的机器上,以下成立

static_assert(std::numeric_limits<std::size_t>::max() > std::numeric_limits<long>::max());

例如,这样的数组将是这样的:

std::vector<int> v(std::numeric_limits<std::size_t>::max());

我想知道k接下来会发生什么:

auto w = v | std::ranges::views::enumerate;
for (auto [k, _] : w) {
    std::cout << k << std::endl;
}

不幸的是,std::vector似乎根本不允许这种大小,抛出异常,而具有std::array相同大小的甚至无法编译(完整示例在这里),但我认为这两种行为都不是标准所要求的。或者是?

c++
  • 1 个回答
  • 63 Views
Martin Hope
Enlico
Asked: 2024-10-24 01:15:17 +0800 CST

当使用 -O0 编译调用该函数的 TU 时,执行 `std::vector<T>::operator[]` 的二进制代码在哪里?

  • 6

如果我使用以下方式编译-O3

#include <vector>
int foo() {
    std::vector<int> const v{17,2,3};
    return v[0] + v[2];
}

我得到的组装是

foo():
        mov     eax, 20
        ret

(至少对于 Clang 来说)。

并且我了解当被其他 TU 调用时它将如何工作。

但是如果我用 进行编译-O0,我会得到这个:

_Z3foov:
        pushq   %rbp
        movq    %rsp, %rbp
        subq    $112, %rsp
        movl    $17, -84(%rbp)
        movl    $2, -80(%rbp)
        movl    $3, -76(%rbp)
        leaq    -84(%rbp), %rax
        movq    %rax, -72(%rbp)
        movq    $3, -64(%rbp)
        leaq    -85(%rbp), %rcx
        movq    %rcx, -32(%rbp)
        movq    -32(%rbp), %rax
        movq    %rax, -8(%rbp)
        movq    -72(%rbp), %rsi
        movq    -64(%rbp), %rdx
        leaq    -56(%rbp), %rdi
        callq   _ZNSt6vectorIiSaIiEEC2ESt16initializer_listIiERKS0_
        jmp     .LBB0_1
.LBB0_1:
        leaq    -85(%rbp), %rax
        movq    %rax, -24(%rbp)
        leaq    -56(%rbp), %rdi
        xorl    %eax, %eax
        movl    %eax, %esi
        callq   _ZNKSt6vectorIiSaIiEEixEm
        movl    (%rax), %eax
        movl    %eax, -108(%rbp)
        leaq    -56(%rbp), %rdi
        movl    $2, %esi
        callq   _ZNKSt6vectorIiSaIiEEixEm
        movq    %rax, %rcx
        movl    -108(%rbp), %eax
        addl    (%rcx), %eax
        movl    %eax, -104(%rbp)
        leaq    -56(%rbp), %rdi
        callq   _ZNSt6vectorIiSaIiEED2Ev
        movl    -104(%rbp), %eax
        addq    $112, %rsp
        popq    %rbp
        retq
        movq    %rax, %rcx
        movl    %edx, %eax
        movq    %rcx, -96(%rbp)
        movl    %eax, -100(%rbp)
        leaq    -85(%rbp), %rax
        movq    %rax, -16(%rbp)
        movq    -96(%rbp), %rdi
        callq   _Unwind_Resume@PLT

__clang_call_terminate:
        pushq   %rbp
        movq    %rsp, %rbp
        callq   __cxa_begin_catch@PLT
        callq   _ZSt9terminatev@PLT

.L.str:
        .asciz  "cannot create std::vector larger than max_size()"

.L.str.1:
        .asciz  "/opt/compiler-explorer/gcc-snapshot/lib/gcc/x86_64-linux-gnu/15.0.0/../../../../include/c++/15.0.0/bits/stl_vector.h"

.L__PRETTY_FUNCTION__._ZNKSt6vectorIiSaIiEEixEm:
        .asciz  "const_reference std::vector<int>::operator[](size_type) const [_Tp = int, _Alloc = std::allocator<int>]"

.L.str.2:
        .asciz  "__n < this->size()"

DW.ref.__gxx_personality_v0:
        .quad   __gxx_personality_v0

在计算和_ZNKSt6vectorIiSaIiEEixEm时调用的在哪里。但我真的不明白那些 2跳到了哪里。我的意思是,字符串只出现了 3 次,其中 2 次是,还有一次是这样的:v[0]v[2]call_ZNKSt6vectorIiSaIiEEixEmcalls

.L__PRETTY_FUNCTION__._ZNKSt6vectorIiSaIiEEixEm:
        .asciz  "const_reference std::vector<int>::operator[](size_type) const [_Tp = int, _Alloc = std::allocator<int>]"

这怎么可能导致元素从向量中拉出来?

或者换句话说,如果我将上述 TU 与另一个调用它并返回值的 TU 链接起来,例如

int foo();
int main() {
  return foo();
}

我在哪里可以找到执行的代码v[0]?v[2]如果它不在程序集中,它甚至不在二进制文件中,对吗?那么程序如何运行?

c++
  • 1 个回答
  • 47 Views
Martin Hope
Enlico
Asked: 2024-09-24 14:33:31 +0800 CST

为什么只返回有状态 lambda 的函数会被编译为任何程序集?

  • 12

以下非模板函数(或者是?)返回非通用的、有状态的 lambda,

auto foo(double a) {
    return [a](double b) -> double {
        return a + b;
    };
}

使用 GCC 或 Clang 编译成这个。为什么?

foo(double):
        ret

我预计它根本不会产生任何输出。


问题的由来

我甚至不记得为什么我开始写上面的片段,但无论如何......

add最初我认为它应该编译成稍微长一点的东西,因为我至少希望看到一条指令。

但后来我意识到foo,上述内容不能仅仅在 cpp 中单独编译,然后链接到使用它的另一个 TU,因为我甚至不能为它写一个声明!

因此我认为将该函数写在非头文件中的唯一原因是它在该非头文件中使用,此时编译器可以在使用它的任何地方内联它。

但如果是这样的话……那么为什么要将其复制foo到任何其他内容,如果它是 TU 中唯一的东西?没有任何内容可以链接,那么为什么会为其生成任何输出?


使用struct+operator()不会改变任何东西,因为这个

struct Bar {
    double a;
    double operator()(double b) const {
        return a + b;
    }
};

Bar bar(double a) {
    return Bar{a};
}

生成相同的-only 代码,这在事后看来也是显而易见的,因为如果隐藏在 cpp 文件中,ret则没有办法bar从其他 TU 链接到此函数。Bar

c++
  • 1 个回答
  • 304 Views
Martin Hope
Enlico
Asked: 2024-08-02 23:47:14 +0800 CST

什么决定了哪个 GHC(以及 Cabal、HLS 等)版本在 GHCup 中被标记为“推荐”?

  • 8

我倾向于坚持“推荐”版本:

┌──────────────────────────────────GHCup──────────────────────────────────┐
│    Tool  Version         Tags                          Notes            │
│─────────────────────────────────────────────────────────────────────────│
│✔✔  GHCup 0.1.30.0   latest,recommended                                  │
│─────────────────────────────────────────────────────────────────────────│
│✗   Stack 2.15.7     latest                                              │
│✗   Stack 2.15.5     recommended                                         │
│✗   Stack 2.15.3                                                         │
│─────────────────────────────────────────────────────────────────────────│
│✗   HLS   2.9.0.1    latest                                              │
│✗   HLS   2.9.0.0                                                        │
│✗   HLS   2.8.0.0                                                        │
│✔✔  HLS   2.7.0.0    recommended                                         │
│✗   HLS   2.6.0.0                                                        │
│─────────────────────────────────────────────────────────────────────────│
│✗   cabal 3.12.1.0   latest                                              │
│✔✔  cabal 3.10.3.0   recommended                                         │
│✗   cabal 3.6.2.0-p1                                                     │
│─────────────────────────────────────────────────────────────────────────│
│✗   GHC   9.10.1     latest,base-4.20.0.0                                │
│✗   GHC   9.8.2      base-4.19.1.0                 hls-powered,2024-02-23│
│✗   GHC   9.6.6      base-4.18.2.1                                       │
│✗   GHC   9.6.5      base-4.18.2.1                                       │
│✔✔  GHC   9.4.8      recommended,base-4.17.2.1     hls-powered           │
│✗   GHC   9.2.8      base-4.16.4.0                 hls-powered           │
│✗   GHC   9.0.2      base-4.15.1.0                                       │
│✗   GHC   8.10.7     base-4.14.3.0                                       │
│✗   GHC   8.8.4      base-4.13.0.0                                       │
│✗   GHC   8.6.5      base-4.12.0.0                                       │
│✗   GHC   8.4.4      base-4.11.1.0                                       │
└─────────────────────────────────────────────────────────────────────────┘

然而,我似乎在 Hackage 上构建时遇到了麻烦,也许是因为Hackage 使用的 GHC比 GHCup 告诉我的推荐版本要新。

那么“推荐”的意义何在?它从何而来?

haskell
  • 1 个回答
  • 33 Views
Martin Hope
Enlico
Asked: 2024-08-02 14:42:44 +0800 CST

为什么 ranges::views::remove_if | ranges::to_vector 和 ranges::actions::remove_if 会生成不同的代码?我应该选择哪一个?为什么?

  • 7

拿着它

#include <range/v3/view/remove_if.hpp>
#include <range/v3/range/conversion.hpp>
#include <vector>

std::vector<int> foo(std::vector<int> v, bool(*p)(int)) {
    return v | ranges::views::remove_if(p) | ranges::to_vector;
}

与此相比

#include <range/v3/action/remove_if.hpp>
#include <vector>

std::vector<int> bar(std::vector<int> v, bool(*p)(int)) {
    return std::move(v) | ranges::actions::remove_if(p);
}

没有模板,只有两个 TU,每个 TU 都提供具有相同签名的纯函数。考虑到它们的实现,从调用者的角度来看,我期望这两个函数完成相同的任务。而他们似乎确实做到了这一点。

然而,它们编译后的代码完全不同,以至于 GCC(至少是主干)为后者生成更短的代码,而 Clang(主干)为前者生成更短的代码。

除了“编译器很难为这两个函数生成相同的代码”之外,我看不出这两个函数编译成不同代码的任何理由,但是什么让它变得如此困难?或者,如果我错了,为什么这两个函数必须编译成不同的汇编?

并且,除了基准测试之外,还有其他理由说明我为什么应该选择其中一种实现方式而不是另一种实现方式吗?

完整例子。

c++
  • 1 个回答
  • 74 Views
Martin Hope
Enlico
Asked: 2024-05-21 16:08:53 +0800 CST

这段代码中有什么错误,或者 MSVC 中有什么错误?[复制]

  • 6
这个问题在这里已经有答案了:
导出类时编译器错误 (1 个回答)
17 小时前关闭。

这是代码片段

#include<memory>
#include<unordered_map>

struct
__declspec(dllexport)
Foo {
    std::unordered_map<const int*, std::unique_ptr<int>> foo;
};

Foo foo();

失败如下:

C:/data/msvc/14.39.33321-Pre/include\list(1299): error C2679: binary '=': no operator found which takes a right-hand operand of type 'const std::pair<const int *const ,std::unique_ptr<int,std::default_delete<int>>>' (or there is no acceptable conversion)
C:/data/msvc/14.39.33321-Pre/include\utility(315): note: could be 'std::pair<_Kty,_Ty> &std::pair<_Kty,_Ty>::operator =(volatile const std::pair<_Kty,_Ty> &)'
        with
        [
            _Kty=const int *,
            _Ty=std::unique_ptr<int,std::default_delete<int>>
        ]
C:/data/msvc/14.39.33321-Pre/include\list(1299): note: 'std::pair<_Kty,_Ty> &std::pair<_Kty,_Ty>::operator =(volatile const std::pair<_Kty,_Ty> &)': cannot convert argument 2 from 'const std::pair<const int *const ,std::unique_ptr<int,std::default_delete<int>>>' to 'volatile const std::pair<_Kty,_Ty> &'
        with
        [
            _Kty=const int *,
            _Ty=std::unique_ptr<int,std::default_delete<int>>
        ]
C:/data/msvc/14.39.33321-Pre/include\list(1299): note: Reason: cannot convert from 'const std::pair<const int *const ,std::unique_ptr<int,std::default_delete<int>>>' to 'volatile const std::pair<_Kty,_Ty>'
        with
        [
            _Kty=const int *,
            _Ty=std::unique_ptr<int,std::default_delete<int>>
        ]

... see linked example for complete error

我确实看到问题是std::unique_ptr不可复制的事实,正如所暗示的那样,例如,通过Reason: cannot convert from 'const std::pair<const int *const ,std::unique_ptr<int,std::default_delete<int>>>' to 'volatile const std::pair<_Kty,_Ty>',但为什么仅在 Windows 上需要可复制性并且仅使用dllexport?


在从中提取的代码库中,__declspec(dllexport)是宏的扩展,该宏在其他平台上扩展为不同的值,例如__attribute__ ((visibility("default")))在 Linux 上,在这种情况下代码编译得很好。

c++
  • 1 个回答
  • 84 Views
Martin Hope
Enlico
Asked: 2024-04-10 04:03:23 +0800 CST

我可以使用 StateT/MaybeT/forever 来消除此 IO 操作中的显式递归吗?

  • 8

我有一个这样的程序

start :: [Q] -> R -> IO R
start qs = fix $ \recurse r -> do
  q <- select qs
  (r', exit) <- askQ q r
  (if exit
    then return
    else recurse) r'

它需要一个问题列表Q、一个Report,并在 monad 中返回一个新的Report,IO因为select需要它随机选择一个问题(也因为askQ将等待用户键盘输入);但是,用户没有选择exit执行时askQ,start会递归调用自身。(fix $ \recurse这是编写递归 lambda 的技巧。)

上面的代码听起来很像以下几件事:

  • monad State,或者更恰当地说,monad 转换器,StateT因为在的递归过程R中不断演化;start
  • 应用组合forever器,因为start是递归的,并且如果用户愿意的话(即,如果他们从不要求退出),可能会永远运行;
  • MaybeTmonad 变压器,因为根据我在实现Maybe之前读到的内容,实现MonadPlus应该能够短路。foreverforever

但无法真正判断是否可以使用这些抽象中的任何一个或多个以更惯用的方式编写上述代码,最重要的是避免显式递归。

haskell
  • 2 个回答
  • 58 Views
Martin Hope
Enlico
Asked: 2024-04-07 02:00:15 +0800 CST

要在 a -> ReaderT r IO b 类型的函数中保留状态,我唯一的选择是将 IORef 放入闭包中吗?或者我可以以某种方式使用 StateT 吗?

  • 7

假设我必须实现一个功能

f :: Foo -> ReaderT Bar IO Baz

我必须传递给消费者(即我将调用c f),其中//Foo被强加给函数的消费者,并且消费者将使用不同的s 重复调用该函数。BarBazFoo

我是否有机会在连续调用之间保留一些本地f状态?


在某种程度上,我已经通过IORef更改签名来实现这一点f

f :: IORef S -> Foo -> ReaderT Client IO a

并将其部分应用到我预先初始化的某种状态:

s <- initState :: IO (IORef S)
c $ f s

这样,完全应用的可以通过一些pdate 函数f改变monads中的 tate :IOatomicModifyIORef'u

f s x = do
  liftIO $ atomicModifyIORef' s u
  -- ...

然而,上面的解决方案对我来说很自然,因为state 确实是一个全局可变状态,它不仅可以通过调用来修改f,还可以通过与 并发运行的程序的其他部分来修改f。

但现在我需要f拥有一些不能被其他任何人修改的私有状态。这让我想到了StateT变压器,但我真的不知道我可以在这种情况下应用它。


手头的实际用例是我想要notify在实现通知服务器的上下文中使用有状态函数。请参阅此处的实现示例。在这种情况下,Foo -> ReaderT Bar IO Baz实际上是MethodCall -> ReaderT Client IO Reply( ReaderT Client IO ais DBusR a)。

正如您所看到的,无论我对 做什么notify,我最终都必须将一个MethodCall -> DBusR Reply函数传递给export,client并且该函数将被多次调用,并且预计每次都会返回,所以在我看来,保持状态的唯一方法是它的闭包,即我必须notify在 之前再提供一个参数MethodCall,并将其部分应用于初始状态,正如我上面所解释的。MethodCall每次传递a 时改变状态的唯一方法是让第一个附加参数成为真正可变的状态,例如IORef我上面提到的。

是这个吗?

haskell
  • 1 个回答
  • 51 Views
Martin Hope
Enlico
Asked: 2024-02-18 00:13:50 +0800 CST

如果我显式启用 DerivingStrategies,为什么 Cabal 与 GHC 不同,不会自动启用 GeneralizedNewtypeDeriving?

  • 7

从文档中,我倾向于认为,如果启用该DerivingStrategies扩展,我不需要启用GeneralizedNewtypeDerivingor DeriveAnyClass,也不需要启用我当前在§6.6.7.1之前列出的任何其他扩展,例如DerivingVia。

然而,这个玩具示例

{-# LANGUAGE DerivingStrategies #-}

newtype MyNum = MyNum Int
 deriving stock (Eq, Ord, Show, Read)
 deriving newtype (Num, Enum, Real, Integral)

main :: IO ()
main = print $ MyNum 0

通过(GHC 9.4.8)编译得很好ghc this-file.hs,但不能通过cabal build(Cabal 3.10.2.1),因为在后一种情况下,还需要我添加

{-# LANGUAGE GeneralizedNewtypeDeriving #-}

有什么线索吗?


foo.cabal我正在使用的虚拟文件是

cabal-version:   3.8
name:            foo
version:         1.0

executable foo
    main-is:          main.hs
    build-depends:    base
haskell
  • 1 个回答
  • 34 Views

Sidebar

Stats

  • 问题 205573
  • 回答 270741
  • 最佳答案 135370
  • 用户 68524
  • 热门
  • 回答
  • Marko Smith

    重新格式化数字,在固定位置插入分隔符

    • 6 个回答
  • Marko Smith

    为什么 C++20 概念会导致循环约束错误,而老式的 SFINAE 不会?

    • 2 个回答
  • Marko Smith

    VScode 自动卸载扩展的问题(Material 主题)

    • 2 个回答
  • Marko Smith

    Vue 3:创建时出错“预期标识符但发现‘导入’”[重复]

    • 1 个回答
  • Marko Smith

    具有指定基础类型但没有枚举器的“枚举类”的用途是什么?

    • 1 个回答
  • Marko Smith

    如何修复未手动导入的模块的 MODULE_NOT_FOUND 错误?

    • 6 个回答
  • Marko Smith

    `(表达式,左值) = 右值` 在 C 或 C++ 中是有效的赋值吗?为什么有些编译器会接受/拒绝它?

    • 3 个回答
  • Marko Smith

    在 C++ 中,一个不执行任何操作的空程序需要 204KB 的堆,但在 C 中则不需要

    • 1 个回答
  • Marko Smith

    PowerBI 目前与 BigQuery 不兼容:Simba 驱动程序与 Windows 更新有关

    • 2 个回答
  • Marko Smith

    AdMob:MobileAds.initialize() - 对于某些设备,“java.lang.Integer 无法转换为 java.lang.String”

    • 1 个回答
  • Martin Hope
    Fantastic Mr Fox msvc std::vector 实现中仅不接受可复制类型 2025-04-23 06:40:49 +0800 CST
  • Martin Hope
    Howard Hinnant 使用 chrono 查找下一个工作日 2025-04-21 08:30:25 +0800 CST
  • Martin Hope
    Fedor 构造函数的成员初始化程序可以包含另一个成员的初始化吗? 2025-04-15 01:01:44 +0800 CST
  • Martin Hope
    Petr Filipský 为什么 C++20 概念会导致循环约束错误,而老式的 SFINAE 不会? 2025-03-23 21:39:40 +0800 CST
  • Martin Hope
    Catskul C++20 是否进行了更改,允许从已知绑定数组“type(&)[N]”转换为未知绑定数组“type(&)[]”? 2025-03-04 06:57:53 +0800 CST
  • Martin Hope
    Stefan Pochmann 为什么 {2,3,10} 和 {x,3,10} (x=2) 的顺序不同? 2025-01-13 23:24:07 +0800 CST
  • Martin Hope
    Chad Feller 在 5.2 版中,bash 条件语句中的 [[ .. ]] 中的分号现在是可选的吗? 2024-10-21 05:50:33 +0800 CST
  • Martin Hope
    Wrench 为什么双破折号 (--) 会导致此 MariaDB 子句评估为 true? 2024-05-05 13:37:20 +0800 CST
  • Martin Hope
    Waket Zheng 为什么 `dict(id=1, **{'id': 2})` 有时会引发 `KeyError: 'id'` 而不是 TypeError? 2024-05-04 14:19:19 +0800 CST
  • Martin Hope
    user924 AdMob:MobileAds.initialize() - 对于某些设备,“java.lang.Integer 无法转换为 java.lang.String” 2024-03-20 03:12:31 +0800 CST

热门标签

python javascript c++ c# java typescript sql reactjs html

Explore

  • 主页
  • 问题
    • 最新
    • 热门
  • 标签
  • 帮助

Footer

AskOverflow.Dev

关于我们

  • 关于我们
  • 联系我们

Legal Stuff

  • Privacy Policy

Language

  • Pt
  • Server
  • Unix

© 2023 AskOverflow.DEV All Rights Reserve